slack-smart-bot 1.11.0 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +44 -3
  3. data/lib/slack/smart-bot/comm/respond.rb +8 -2
  4. data/lib/slack/smart-bot/comm/set_status.rb +21 -0
  5. data/lib/slack/smart-bot/comm.rb +1 -0
  6. data/lib/slack/smart-bot/commands/general/add_memo_team.rb +117 -0
  7. data/lib/slack/smart-bot/commands/general/add_vacation.rb +51 -0
  8. data/lib/slack/smart-bot/commands/general/delete_memo_team.rb +69 -0
  9. data/lib/slack/smart-bot/commands/general/delete_team.rb +21 -1
  10. data/lib/slack/smart-bot/commands/general/remove_vacation.rb +27 -0
  11. data/lib/slack/smart-bot/commands/general/see_announcements.rb +1 -1
  12. data/lib/slack/smart-bot/commands/general/see_statuses.rb +1 -1
  13. data/lib/slack/smart-bot/commands/general/see_teams.rb +182 -32
  14. data/lib/slack/smart-bot/commands/general/see_vacations.rb +58 -0
  15. data/lib/slack/smart-bot/commands/general/see_vacations_team.rb +136 -0
  16. data/lib/slack/smart-bot/commands/general/set_memo_status.rb +58 -0
  17. data/lib/slack/smart-bot/commands/general/update_team.rb +22 -1
  18. data/lib/slack/smart-bot/commands/general_bot_commands.rb +172 -4
  19. data/lib/slack/smart-bot/commands/on_bot/general/bot_stats.rb +21 -5
  20. data/lib/slack/smart-bot/commands/on_bot/kill_repl.rb +32 -0
  21. data/lib/slack/smart-bot/commands/on_bot/repl.rb +1 -0
  22. data/lib/slack/smart-bot/commands/on_bot/run_repl.rb +113 -33
  23. data/lib/slack/smart-bot/commands/on_master/admin_master/publish_announcements.rb +3 -1
  24. data/lib/slack/smart-bot/commands.rb +8 -0
  25. data/lib/slack/smart-bot/process.rb +17 -7
  26. data/lib/slack/smart-bot/treat_message.rb +11 -1
  27. data/lib/slack/smart-bot/utils/check_vacations.rb +43 -0
  28. data/lib/slack/smart-bot/utils/get_admins_channels.rb +23 -3
  29. data/lib/slack/smart-bot/utils/get_command_ids.rb +1 -1
  30. data/lib/slack/smart-bot/utils/get_help.rb +4 -3
  31. data/lib/slack/smart-bot/utils/get_vacations.rb +22 -0
  32. data/lib/slack/smart-bot/utils/save_stats.rb +9 -2
  33. data/lib/slack/smart-bot/utils/save_status.rb +1 -1
  34. data/lib/slack/smart-bot/utils/update_admins_channels.rb +20 -3
  35. data/lib/slack/smart-bot/utils/update_vacations.rb +16 -0
  36. data/lib/slack/smart-bot/utils.rb +3 -0
  37. data/lib/slack-smart-bot.rb +22 -2
  38. data/whats_new.txt +12 -17
  39. metadata +19 -7
@@ -76,7 +76,7 @@ def general_bot_commands(user, command, dest, files = [])
76
76
  # help: command_id: :add_announcement
77
77
  # help:
78
78
  when /\A\s*(add|create)\s+(red\s+|green\s+|white\s+|yellow\s+)?(announcement|statement|declaration|message)\s+(.+)\s*\z/i,
79
- /\A\s*(add|create)\s+(:\w+:)\s+(announcement|statement|declaration|message)\s+(.+)\s*\z/i
79
+ /\A\s*(add|create)\s+(:[\w\-]+:)\s+(announcement|statement|declaration|message)\s+(.+)\s*\z/i
80
80
  type = $2.to_s.downcase.strip
81
81
  type = 'white' if type == ''
82
82
  message = $4
@@ -122,10 +122,10 @@ def general_bot_commands(user, command, dest, files = [])
122
122
  # help: <https://github.com/MarioRuiz/slack-smart-bot#announcements|more info>
123
123
  # help: command_id: :see_announcements
124
124
  # help:
125
- when /\A\s*see\s+(red\s+|green\s+|white\s+|yellow\s+|:\w+:\s+)?(announcements|statements|declarations|messages)()\s*\z/i,
125
+ when /\A\s*see\s+(red\s+|green\s+|white\s+|yellow\s+|:[\w\-]+:\s+)?(announcements|statements|declarations|messages)()\s*\z/i,
126
126
  /\A\s*see\s+(all\s+)?(announcements|statements|declarations|messages)()\s*\z/i,
127
- /\A\s*see\s+(red\s+|green\s+|white\s+|yellow\s+|:\w+:\s+)?(announcements|statements|declarations|messages)\s+#([\w\-]+)\s*\z/i,
128
- /\A\s*see\s+(red\s+|green\s+|white\s+|yellow\s+|:\w+:\s+)?(announcements|statements|declarations|messages)\s+<#(\w+)\|.*>\s*\z/i
127
+ /\A\s*see\s+(red\s+|green\s+|white\s+|yellow\s+|:[\w\-]+:\s+)?(announcements|statements|declarations|messages)\s+#([\w\-]+)\s*\z/i,
128
+ /\A\s*see\s+(red\s+|green\s+|white\s+|yellow\s+|:[\w\-]+:\s+)?(announcements|statements|declarations|messages)\s+<#(\w+)\|.*>\s*\z/i
129
129
 
130
130
  type = $1.to_s.downcase.strip
131
131
  channel = $3.to_s
@@ -387,6 +387,94 @@ def general_bot_commands(user, command, dest, files = [])
387
387
  info = Thread.current[:command_orig].to_s.gsub("\u00A0", " ").scan(/^[^:]+:\s*(.+)\s*$/im).join
388
388
  add_team(user, name, options, info)
389
389
 
390
+ # help: ----------------------------------------------
391
+ # help: `add TYPE to TEAM_NAME team : MESSAGE`
392
+ # help: `add private TYPE to TEAM_NAME team : MESSAGE`
393
+ # help: `add personal TYPE to TEAM_NAME team : MESSAGE`
394
+ # help: `add TYPE to TEAM_NAME team TOPIC : MESSAGE`
395
+ # help: `add private TYPE to TEAM_NAME team TOPIC : MESSAGE`
396
+ # help: `add personal TYPE to TEAM_NAME team TOPIC : MESSAGE`
397
+ # help: It will add a memo to the team. The memos will be displayed with the team info.
398
+ # help: Only team members can add a memo.
399
+ # help: TYPE: memo, note, issue, task, feature, bug, jira, github
400
+ # help: TOPIC: one word, a-z, A-Z, 0-9, - and _
401
+ # help: If private then the memo will be only displayed to team members on a DM or the members channel.
402
+ # help: If personal then the memo will be only displayed to the creator on a DM.
403
+ # help: In case jira type supplied:
404
+ # help: The message should be an JQL URL, JQL string or an issue URL.
405
+ # help: To be able to use it you need to specify on the SmartBot settings the credentials.
406
+ # help: In case no TOPIC is supplied then it will create automatically the topics from the labels specified on every JIRA issue
407
+ # help: In case github type supplied:
408
+ # help: The message should be a github URL. You can filter by state (open/closed/all) and labels
409
+ # help: To be able to use it you need to specify on the SmartBot settings the github token.
410
+ # help: In case no TOPIC is supplied then it will create automatically the topics from the labels specified on every Github issue
411
+ # help: Examples:
412
+ # help: _add memo to sales team : Add tests for Michigan feature_
413
+ # help: _add private note to sales team : Bills will need to be deployed before Friday_
414
+ # help: _add memo to dev team web : Check last version_
415
+ # help: _add private bug to dev team SRE : Logs should not be accessible from outside VPN_
416
+ # help: _add memo sales team : Add tests for Michigan feature_
417
+ # help: _add memo team sales: Add tests for Michigan feature_
418
+ # help: _add personal memo team sales: Check my vacations_
419
+ # help: _add jira to sales team : labels = SalesT AND status != Done_
420
+ # help: _add github to sales team : PeterBale/SalesBoom/issues?state=open&labels=bug_
421
+ # help: _add github to sales team dev: PeterBale/DevProject/issues/71_
422
+ # help: <https://github.com/MarioRuiz/slack-smart-bot#teams|more info>
423
+ # help: command_id: :add_memo_team
424
+ # help:
425
+ when /\A\s*add\s+(private\s+|personal\s+)?(memo|note|issue|task|feature|bug|jira|github)\s+(to\s+)?team\s+([\w\-]+)\s*([^:]+)?\s*:\s+(.+)\s*\z/im,
426
+ /\A\s*add\s+(private\s+|personal\s+)?(memo|note|issue|task|feature|bug|jira|github)\s+(to\s+)?([\w\-]+)\s+team\s*([^:]+)?\s*:\s+(.+)\s*\z/im
427
+ privacy = $1.to_s.strip.downcase
428
+ type = $2.downcase
429
+ team_name = $4.downcase
430
+ topic = $5.to_s.strip
431
+ message = Thread.current[:command_orig].to_s.gsub("\u00A0", " ").scan(/^[^:]+:\s*(.+)\s*$/im).join
432
+ add_memo_team(user, privacy, team_name, topic, type, message)
433
+
434
+ # help: ----------------------------------------------
435
+ # help: `delete memo ID from TEAM_NAME team`
436
+ # help: It will delete the supplied memo ID on the team specified.
437
+ # help: aliases for memo: note, issue, task, feature, bug, jira, github
438
+ # help: You have to be a member of the team, the creator or a Master admin to be able to delete a memo.
439
+ # help: Examples:
440
+ # help: _delete memo 32 from sales team_
441
+ # help: <https://github.com/MarioRuiz/slack-smart-bot#teams|more info>
442
+ # help: command_id: :delete_memo_team
443
+ # help:
444
+ when /\A\s*(delete|remove)\s+(memo|note|issue|task|feature|bug|jira|github)\s+(\d+)\s+(from|on)\s+team\s+([\w\-]+)\s*\z/i,
445
+ /\A\s*(delete|remove)\s+(memo|note|issue|task|feature|bug|jira|github)\s+(\d+)\s+(from|on)\s+([\w\-]+)\s+team\s*\z/i
446
+ memo_id = $3
447
+ team_name = $5.downcase
448
+ delete_memo_team(user, team_name, memo_id)
449
+
450
+ # help: ----------------------------------------------
451
+ # help: `set memo ID on TEAM_NAME team STATUS`
452
+ # help: `set STATUS on memo ID TEAM_NAME team`
453
+ # help: It will assign to the ID specified the emoticon status indicated.
454
+ # help: aliases for memo: note, issue, task, feature, bug
455
+ # help: This command will be only for memo, note, issue, task, feature, bug. Not for jira or github.
456
+ # help: You have to be a member of the team, the creator or a Master admin to be able to set a status.
457
+ # help: Examples:
458
+ # help: _set memo 32 on sales team :runner:_
459
+ # help: _set bug 7 on team sales :heavy_check_mark:_
460
+ # help: _set :runner: on memo 6 sales team_
461
+ # help: <https://github.com/MarioRuiz/slack-smart-bot#teams|more info>
462
+ # help: command_id: :set_memo_status
463
+ # help:
464
+ when /\A\s*(set)\s+(memo|note|issue|task|feature|bug)\s+(\d+)\s+on\s+team\s+([\w\-]+)\s+(:[\w\-]+:)\s*\z/i,
465
+ /\A\s*(set)\s+(memo|note|issue|task|feature|bug)\s+(\d+)\s+on\s+([\w\-]+)\s+team\s+(:[\w\-]+:)\s*\z/i
466
+ memo_id = $3
467
+ team_name = $4.downcase
468
+ status = $5
469
+ set_memo_status(user, team_name, memo_id, status)
470
+
471
+ when /\A\s*(set)\s+(:[\w\-]+:)\s+on\s+(memo|note|issue|task|feature|bug)\s+(\d+)\s+team\s+([\w\-]+)\s*\z/i,
472
+ /\A\s*(set)\s+(:[\w\-]+:)\s+on\s+(memo|note|issue|task|feature|bug)\s+(\d+)\s+([\w\-]+)\s+team\s*\z/i
473
+ memo_id = $4
474
+ team_name = $5.downcase
475
+ status = $2
476
+ set_memo_status(user, team_name, memo_id, status)
477
+
390
478
  # help: ----------------------------------------------
391
479
  # help: `see teams`
392
480
  # help: `see team TEAM_NAME`
@@ -488,6 +576,86 @@ def general_bot_commands(user, command, dest, files = [])
488
576
  name = $2.downcase
489
577
  delete_team(user, name)
490
578
 
579
+
580
+ # help: ----------------------------------------------
581
+ # help: `add vacation from YYYY/MM/DD to YYYY/MM/DD`
582
+ # help: `add vacation YYYY/MM/DD`
583
+ # help: `add sick from YYYY/MM/DD to YYYY/MM/DD`
584
+ # help: `add sick YYYY/MM/DD`
585
+ # help: `add sick child YYYY/MM/DD`
586
+ # help: It will add the supplied period to your plan.
587
+ # help: Instead of YYYY/MM/DD you can use 'today' or 'tomorrow' or 'next week'
588
+ # help: To see your plan call `see my time off`
589
+ # help: If you want to see the vacation plan for the team `see team NAME`
590
+ # help: Also you can see the vacation plan for the team for a specific period: `vacations team NAME YYYY/MM/DD`
591
+ # help: The SmartBot will automatically set the users status to :palm_tree:, :baby: or :face_with_thermometer: and the expiration date.
592
+ # help: Examples:
593
+ # help: _add vacation from 2022/10/01 to 2022/10/22_
594
+ # help: _add sick 2022/08/22_
595
+ # help: _add vacation tomorrow_
596
+ # help: _add sick baby today_
597
+ # help: <https://github.com/MarioRuiz/slack-smart-bot#time-off-management|more info>
598
+ # help: command_id: :add_vacation
599
+ # help:
600
+ when /\A\s*add\s+(sick|vacation|sick\s+baby|sick\s+child)\s+from\s+(\d\d\d\d\/\d\d\/\d\d)\s+to\s+(\d\d\d\d\/\d\d\/\d\d)\s*\z/i,
601
+ /\A\s*add\s+(sick|vacation|sick\s+baby|sick\s+child)\s+from\s+(\d\d\d\d-\d\d-\d\d)\s+to\s+(\d\d\d\d-\d\d-\d\d)\s*\z/i,
602
+ /\A\s*add\s+(sick|vacation|sick\s+baby|sick\s+child)\s+(\d\d\d\d-\d\d-\d\d)()\s*\z/i,
603
+ /\A\s*add\s+(sick|vacation|sick\s+baby|sick\s+child)\s+(\d\d\d\d\/\d\d\/\d\d)()\s*\z/i,
604
+ /\A\s*add\s+(sick|vacation|sick\s+baby|sick\s+child)\s+(today|tomorrow|next\sweek)()\s*\z/i
605
+ type = $1
606
+ from = $2.downcase
607
+ to = $3
608
+ add_vacation(user, type, from, to)
609
+
610
+ # help: ----------------------------------------------
611
+ # help: `remove vacation ID`
612
+ # help: `remove vacation period ID`
613
+ # help: `remove sick period ID`
614
+ # help: `remove time off period ID`
615
+ # help: `delete vacation ID`
616
+ # help: It will remove the specified period from your vacations/sick periods.
617
+ # help: Examples:
618
+ # help: _remove vacation 20_
619
+ # help: <https://github.com/MarioRuiz/slack-smart-bot#time-off-management|more info>
620
+ # help: command_id: :remove_vacation
621
+ # help:
622
+ when /\A\s*(delete|remove)\s+(vacation|sick|time\s+off)(\s+period)?\s+(\d+)\s*\z/i
623
+ vacation_id = $4
624
+ remove_vacation(user, vacation_id)
625
+
626
+ # help: ----------------------------------------------
627
+ # help: `see my vacations`
628
+ # help: `see my time off`
629
+ # help: `see vacations @USER`
630
+ # help: It will display current and past time off.
631
+ # help: <https://github.com/MarioRuiz/slack-smart-bot#time-off-management|more info>
632
+ # help: command_id: :see_vacations
633
+ # help:
634
+ when /\A\s*see\s+my\s+vacations\s*()\z/i,
635
+ /\A\s*see\s+my\s+time\s+off\s*()\z/i,
636
+ /\A\s*see\s+time\s+off\s+<@(\w+)>\s*\z/i,
637
+ /\A\s*see\s+vacations\s+<@(\w+)>\s*\z/i
638
+ from_user = $1
639
+ see_vacations(user, from_user: from_user)
640
+
641
+ # help: ----------------------------------------------
642
+ # help: `vacations team NAME`
643
+ # help: `time off team NAME`
644
+ # help: `vacations team NAME YYYY/MM/DD`
645
+ # help: `time off team NAME YYYY/MM/DD`
646
+ # help: It will display the time off plan for the team specified.
647
+ # help: <https://github.com/MarioRuiz/slack-smart-bot#time-off-management|more info>
648
+ # help: command_id: :see_vacations_team
649
+ # help:
650
+ when /\A\s*(see\s+)?(vacations|time\s+off)\s+team\s+([\w\-]+)\s*(\d\d\d\d\/\d\d\/\d\d)?\s*\z/i,
651
+ /\A\s*(see\s+)?(vacations|time\s+off)\s+([\w\-]+)\s+team\s*(\d\d\d\d\/\d\d\/\d\d)?\s*\z/i,
652
+ /\A\s*(see\s+)?(vacations|time\s+off)\s+team\s+([\w\-]+)\s*(\d\d\d\d-\d\d-\d\d)?\s*\z/i,
653
+ /\A\s*(see\s+)?(vacations|time\s+off)\s+([\w\-]+)\s+team\s*(\d\d\d\d-\d\d-\d\d)?\s*\z/i
654
+ team_name = $3.downcase
655
+ date = $4.to_s
656
+ date = Date.today.strftime("%Y/%m/%d") if date.empty?
657
+ see_vacations_team(user, team_name, date)
658
+
491
659
  else
492
660
  return false
493
661
  end
@@ -295,11 +295,27 @@ class SlackSmartBot
295
295
  users_attachment = []
296
296
  if user==''
297
297
  users = rows.user_id.uniq.sort
298
- rows.user_id.each do |usr|
299
- user_info = @users.select { |u| u.id == usr or (u.key?(:enterprise_user) and u.enterprise_user.id == usr) }[-1]
300
- unless user_info.nil? or user_info.is_app_user or user_info.is_bot
301
- tzone_users[user_info.tz_label] ||= 0
302
- tzone_users[user_info.tz_label] += 1
298
+ if rows[0].key?(:time_zone) #then save_stats is saving the time zone already
299
+ rows.time_zone.each do |time_zone|
300
+ unless time_zone == ''
301
+ tzone_users[time_zone] ||= 0
302
+ tzone_users[time_zone] += 1
303
+ end
304
+ end
305
+ else
306
+ rows.user_id.each_with_index do |usr, i|
307
+ if rows[i].values.size >= 12 #then save_stats is saving the time zone already but not all the data
308
+ unless rows[i].values[11] == ''
309
+ tzone_users[rows[i].values[11]] ||= 0
310
+ tzone_users[rows[i].values[11]] += 1
311
+ end
312
+ else
313
+ user_info = @users.select { |u| u.id == usr or (u.key?(:enterprise_user) and u.enterprise_user.id == usr) }[-1]
314
+ unless user_info.nil? or user_info.is_app_user or user_info.is_bot
315
+ tzone_users[user_info.tz_label] ||= 0
316
+ tzone_users[user_info.tz_label] += 1
317
+ end
318
+ end
303
319
  end
304
320
  end
305
321
  if users.size > 10
@@ -0,0 +1,32 @@
1
+ class SlackSmartBot
2
+ # help: ----------------------------------------------
3
+ # help: `kill repl RUN_REPL_ID`
4
+ # help: Will kill a running repl previously executed with 'run repl' command.
5
+ # help: Only the user that run the repl or a master admin can kill the repl.
6
+ # help: Example:
7
+ # help: _kill repl X33JK_
8
+ # help: <https://github.com/MarioRuiz/slack-smart-bot#repl|more info>
9
+ # help: command_id: :kill_repl
10
+ # help:
11
+ def kill_repl(dest, user, repl_id)
12
+ #todo: add tests
13
+ if has_access?(__method__, user)
14
+ save_stats(__method__)
15
+ if !@run_repls.key?(repl_id)
16
+ respond "The run repl with id #{repl_id} doesn't exist"
17
+ elsif @run_repls[repl_id].user != user.name and !config.masters.include?(user.name)
18
+ respond "Only #{@run_repls[repl_id].user} or a master admin can kill this repl."
19
+ else
20
+ pids = `pgrep -P #{@run_repls[repl_id].pid}`.split("\n").map(&:to_i) #todo: it needs to be adapted for Windows
21
+ pids.each do |pd|
22
+ begin
23
+ Process.kill("KILL", pd)
24
+ rescue
25
+ end
26
+ end
27
+ respond "The repl #{@run_repls[repl_id].name} (id: #{repl_id}) has been killed."
28
+ @run_repls.delete(repl_id)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -121,6 +121,7 @@ class SlackSmartBot
121
121
  process_to_run = '
122
122
  ' + env_vars.join("\n") + '
123
123
  require \"amazing_print\"
124
+ require \"stringio\"
124
125
  bindme' + serialt + ' = binding
125
126
  eval(\"require \'nice_http\'\" , bindme' + serialt + ')
126
127
  def ls(obj)
@@ -2,28 +2,39 @@ class SlackSmartBot
2
2
  # help: ----------------------------------------------
3
3
  # help: `run repl SESSION_NAME`
4
4
  # help: `run repl SESSION_NAME ENV_VAR=VALUE ENV_VAR=VALUE`
5
+ # help: `run repl SESSION_NAME PARAMS`
5
6
  # help: `run live SESSION_NAME`
6
7
  # help: `run irb SESSION_NAME`
7
- # help: Will run the repl session specified and return the output.
8
+ # help: Will run the repl session specified and return the output.
8
9
  # help: You can supply the Environmental Variables you need for the Session
10
+ # help: PARAMS: Also it is possible to supply code that will be run before the repl code on the same session.
9
11
  # help: It will return only the values that were print out on the repl with puts, print, p or pp
10
12
  # help: Example:
11
13
  # help: _run repl CreateCustomer LOCATION=spain HOST='https://10.30.40.50:8887'_
12
14
  # help: <https://github.com/MarioRuiz/slack-smart-bot#repl|more info>
13
15
  # help: command_id: :run_repl
14
16
  # help:
15
- def run_repl(dest, user, session_name, env_vars, rules_file)
17
+ def run_repl(dest, user, session_name, env_vars, prerun, rules_file)
16
18
  #todo: add tests
17
19
  from = user.name
18
20
  if has_access?(__method__, user)
19
21
  save_stats(__method__)
20
22
  Dir.mkdir("#{config.path}/repl") unless Dir.exist?("#{config.path}/repl")
21
23
  Dir.mkdir("#{config.path}/repl/#{@channel_id}") unless Dir.exist?("#{config.path}/repl/#{@channel_id}")
24
+ code = prerun.join("\n")
22
25
  if File.exist?("#{config.path}/repl/#{@channel_id}/#{session_name}.run")
23
- if @repls.key?(session_name) and (@repls[session_name][:type] == :private or @repls[session_name][:type] == :private_clean) and
24
- @repls[session_name][:creator_name]!=user.name and
25
- !is_admin?(user.name)
26
+ if @repls.key?(session_name) and (@repls[session_name][:type] == :private or @repls[session_name][:type] == :private_clean) and
27
+ @repls[session_name][:creator_name] != user.name and
28
+ !is_admin?(user.name)
26
29
  respond "The REPL with session name: #{session_name} is private", dest
30
+ elsif !prerun.empty? and (code.match?(/System/i) or code.match?(/Kernel/i) or code.include?("File.") or
31
+ code.include?("`") or code.include?("exec") or code.include?("spawn") or code.include?("IO.") or
32
+ code.match?(/open3/i) or code.match?(/bundle/i) or code.match?(/gemfile/i) or code.include?("%x") or
33
+ code.include?("ENV") or code.match?(/=\s*IO/) or code.include?("Dir.") or
34
+ code.match?(/=\s*File/) or code.match?(/=\s*Dir/) or code.match?(/<\s*File/) or code.match?(/<\s*Dir/) or
35
+ code.match?(/\w+:\s*File/) or code.match?(/\w+:\s*Dir/) or
36
+ code.match?(/=?\s*(require|load)(\(|\s)/i))
37
+ respond "Sorry I cannot run this due security reasons", dest
27
38
  else
28
39
  if @repls.key?(session_name) #not temp
29
40
  @repls[session_name][:accessed] = Time.now.to_s
@@ -32,55 +43,124 @@ class SlackSmartBot
32
43
  else
33
44
  @repls[session_name][:runs_by_others] += 1
34
45
  end
35
- update_repls()
46
+ update_repls()
36
47
  end
37
48
 
38
49
  content = env_vars.join("\n")
39
50
  content += "\nrequire 'nice_http'\n"
40
51
  unless rules_file.empty? # to get the project_folder
41
52
  begin
42
- eval(File.new(config.path+rules_file).read) if File.exist?(config.path+rules_file)
53
+ eval(File.new(config.path + rules_file).read) if File.exist?(config.path + rules_file)
43
54
  end
44
55
  end
45
- if File.exist?("#{project_folder}/.smart-bot-repl") and
46
- ((@repls.key?(session_name) and @repls[session_name][:type] != :private_clean and @repls[session_name][:type] != :public_clean) or !@repls.key?(session_name))
56
+ if File.exist?("#{project_folder}/.smart-bot-repl") and
57
+ ((@repls.key?(session_name) and @repls[session_name][:type] != :private_clean and @repls[session_name][:type] != :public_clean) or !@repls.key?(session_name))
47
58
  content += File.read("#{project_folder}/.smart-bot-repl")
48
59
  content += "\n"
49
60
  end
50
- content += File.read("#{config.path}/repl/#{@channel_id}/#{session_name}.run").gsub(/^(quit|exit|bye)$/i,'') #todo: remove this gsub, it will never contain it
61
+ unless prerun.empty?
62
+ content += prerun.join("\n")
63
+ content += "\n"
64
+ end
65
+ content += File.read("#{config.path}/repl/#{@channel_id}/#{session_name}.run").gsub(/^(quit|exit|bye)$/i, "") #todo: remove this gsub, it will never contain it
51
66
  Dir.mkdir("#{project_folder}/tmp") unless Dir.exist?("#{project_folder}/tmp")
52
67
  Dir.mkdir("#{project_folder}/tmp/repl") unless Dir.exist?("#{project_folder}/tmp/repl")
53
- File.write("#{project_folder}/tmp/repl/#{session_name}.rb", content, mode: "w+")
54
- process_to_run = "ruby ./tmp/repl/#{session_name}.rb"
68
+ if Thread.current[:on_thread]
69
+ # to force stdout.each to be performed every 3 seconds
70
+ content = "Thread.new do
71
+ while true do
72
+ puts ''
73
+ sleep 3
74
+ end
75
+ end
76
+ #{content}
77
+ "
78
+ end
79
+ random = "5:LN&".gen
80
+ File.write("#{project_folder}/tmp/repl/#{session_name}_#{user.name}_#{random}.rb", content, mode: "w+")
81
+ process_to_run = "ruby ./tmp/repl/#{session_name}_#{user.name}_#{random}.rb"
55
82
  process_to_run = ("cd #{project_folder} && #{process_to_run}") if defined?(project_folder)
56
- respond "Running REPL #{session_name}"
57
- stdout, stderr, status = Open3.capture3(process_to_run)
58
- if stderr == ""
59
- if stdout == ""
60
- respond "*#{session_name}*: Nothing returned."
61
- else
62
- if stdout.to_s.lines.count < 60 and stdout.to_s.size < 3500
63
- output = ''
64
- stdout.each_line do |line|
65
- if line.match?(/^{.+}$/) or line.match?(/^\[.+\]$/)
66
- output += "```\n#{line}```\n"
83
+ respond "Running REPL #{session_name} (id: #{random})"
84
+ @run_repls[random] = { user: user.name, name: session_name, pid: '' }
85
+ react :running
86
+
87
+ require "pty"
88
+ timeout = 60 * 60 * 4 # 4 hours
89
+
90
+ started = Time.now
91
+ results = []
92
+ begin
93
+ PTY.spawn(process_to_run) do |stdout, stdin, pid|
94
+ last_result = -1
95
+ last_time = Time.now
96
+ @run_repls[random].pid = pid
97
+ begin
98
+ stdout.each do |line|
99
+ if (Time.now - started) > timeout
100
+ respond "run REPL session finished. Max time reached: #{session_name} (id: #{random})", dest
101
+ pids = `pgrep -P #{pid}`.split("\n").map(&:to_i) #todo: it needs to be adapted for Windows
102
+ pids.each do |pd|
103
+ begin
104
+ Process.kill("KILL", pd)
105
+ rescue
106
+ end
107
+ end
108
+ break
67
109
  else
68
- output +=line
110
+ results << line
111
+ if Thread.current[:on_thread]
112
+ if (Time.now - last_time) > 2
113
+ if (results.size - last_result) < 60 and results[(last_result + 1)..-1].join.size < 3500
114
+ output = ""
115
+ results[(last_result + 1)..-1].each do |li|
116
+ if li.match?(/^\s*{.+}\s*$/) or li.match?(/^\s*\[.+\]\s*$/)
117
+ output += "```\n#{li}```\n"
118
+ else
119
+ output += li
120
+ end
121
+ end
122
+ respond output
123
+ else
124
+ send_file(dest, "", "response.rb", "", "text/plain", "ruby", content: results[(last_result + 1)..-1].join)
125
+ end
126
+ last_result = results.size - 1
127
+ last_time = Time.now
128
+ end
129
+ end
69
130
  end
70
131
  end
71
- respond "*#{session_name}*: #{output}"
132
+ rescue Errno::EIO
133
+ @logger.warn "run_repl PTY Errno:EIO error"
134
+ end
135
+ if results.empty?
136
+ respond "*#{session_name}* (id: #{random}): Nothing returned."
72
137
  else
73
- send_file(dest, "", 'response.rb', "", 'text/plain', "ruby", content: stdout)
138
+ if last_result != (results.size - 1)
139
+ if (results.size - last_result) < 60 and results[(last_result + 1)..-1].join.size < 3500
140
+ output = ""
141
+ results[(last_result + 1)..-1].each do |li|
142
+ if li.match?(/^\s*{.+}\s*$/) or li.match?(/^\s*\[.+\]\s*$/)
143
+ output += "```\n#{li}```\n"
144
+ else
145
+ output += li
146
+ end
147
+ end
148
+ if Thread.current[:on_thread]
149
+ respond output
150
+ else
151
+ respond "*#{session_name}* (id: #{random}):\n#{output}"
152
+ end
153
+ else
154
+ send_file(dest, "", "response.rb", "", "text/plain", "ruby", content: results[(last_result + 1)..-1].join)
155
+ end
156
+ end
74
157
  end
75
158
  end
76
- else
77
- if (stdout.to_s+stderr.to_s).lines.count < 60
78
- respond "*#{session_name}*: #{stdout} #{stderr}"
79
- else
80
- send_file(dest, "", 'response.rb', "", 'text/plain', "ruby", content: (stdout.to_s+stderr.to_s))
81
- end
82
-
159
+ rescue PTY::ChildExited
160
+ @logger.warn "run_repl PTY The child process exited!"
83
161
  end
162
+ @run_repls.delete(random) if @run_repls.key?(random)
163
+ unreact :running
84
164
  end
85
165
  else
86
166
  respond "The REPL with session name: #{session_name} doesn't exist on this Smart Bot Channel", dest
@@ -2,6 +2,7 @@ class SlackSmartBot
2
2
  # helpmaster: ----------------------------------------------
3
3
  # helpmaster: `publish announcements`
4
4
  # helpmaster: It will publish on all channels the announcements added by using 'add announcement' command.
5
+ # helpmaster: It won't be published if less than 11 messages published on the channel since last time this command was called.
5
6
  # helpmaster: Only works if you are on Master channel and you are a master admin user
6
7
  # helpmaster: The messages stored on a DM won't be published.
7
8
  # helpmaster: This is very convenient to be called from a *Routine* for example every weekday at 09:00.
@@ -16,8 +17,9 @@ class SlackSmartBot
16
17
  channels.select! {|i| i[/\.csv$/]}
17
18
  channels.each do |channel|
18
19
  channel.gsub!('.csv','')
19
- unless channel[0]== 'D'
20
+ unless channel[0]== 'D' or (@announcements_activity_after.key?(channel) and @announcements_activity_after[channel] <= 10)
20
21
  see_announcements(user, '', channel, false, true)
22
+ @announcements_activity_after[channel] = 0
21
23
  sleep 0.5 # to avoid reach ratelimit
22
24
  end
23
25
  end
@@ -6,6 +6,7 @@ require_relative "commands/on_bot/ruby_code"
6
6
  require_relative "commands/on_bot/repl"
7
7
  require_relative "commands/on_bot/get_repl"
8
8
  require_relative "commands/on_bot/run_repl"
9
+ require_relative "commands/on_bot/kill_repl"
9
10
  require_relative "commands/on_bot/delete_repl"
10
11
  require_relative "commands/on_bot/see_repls"
11
12
  require_relative "commands/on_bot/general/whats_new"
@@ -59,7 +60,14 @@ require_relative "commands/general/see_access"
59
60
  require_relative "commands/general/allow_access"
60
61
  require_relative "commands/general/deny_access"
61
62
  require_relative "commands/general/add_team"
63
+ require_relative "commands/general/add_memo_team"
64
+ require_relative "commands/general/set_memo_status"
65
+ require_relative "commands/general/delete_memo_team"
62
66
  require_relative "commands/general/see_teams"
63
67
  require_relative "commands/general/update_team"
64
68
  require_relative "commands/general/ping_team"
65
69
  require_relative "commands/general/delete_team"
70
+ require_relative "commands/general/add_vacation"
71
+ require_relative "commands/general/remove_vacation"
72
+ require_relative "commands/general/see_vacations"
73
+ require_relative "commands/general/see_vacations_team"
@@ -152,7 +152,9 @@ class SlackSmartBot
152
152
  see_routines(dest, from, user, all)
153
153
  when /\A\s*get\s+bot\s+logs?\s*$/i
154
154
  get_bot_logs(dest, from, typem)
155
- when /\A\s*send\s+message\s+(on|to|in)\s*(.+)\s*:\s*(.+)\s*$/im
155
+ when /\A\s*send\s+message\s+(on|to|in)\s+<(https?:[^:]+)>\s*:\s*(.+)\s*$/im,
156
+ /\A\s*send\s+message\s+(on|to|in)\s+(https?:[^:]+)\s*:\s*(.+)\s*$/im,
157
+ /\A\s*send\s+message\s+(on|to|in)\s*([^:]+)\s*:\s*(.+)\s*$/im
156
158
  opts = $2
157
159
  message = $3
158
160
  thread_ts = ''
@@ -174,7 +176,7 @@ class SlackSmartBot
174
176
 
175
177
  thread_ts.gsub!('.','')
176
178
  send_message(dest, from, typem, to, thread_ts, message)
177
- when /\A\s*delete\s+message\s+(.+)\s*$/i
179
+ when /\A\s*delete\s+message\s+(http.+)\s*$/i
178
180
  url = $1
179
181
  delete_message(from, typem, url)
180
182
  when /\A\s*react\s+(on|to|in)\s*([^\s]+)\s+([p\d\.]+)\s+(.+)\s*$/i,
@@ -396,11 +398,15 @@ class SlackSmartBot
396
398
  when /\A\s*get\s+(repl|irb|live)\s+([\w\-]+)\s*/i
397
399
  session_name = $2
398
400
  get_repl(dest, user, session_name)
399
- when /\A\s*run\s+(repl|irb|live)\s+([\w\-]+)()\s*$/i,
400
- /^\s*run\s+(repl|irb|live)\s+([\w\-]+)\s+(.+)\s*$/i
401
+ when /\A\s*run\s+(repl|irb|live)\s+([\w\-]+)()\s*\z/im,
402
+ /^\s*run\s+(repl|irb|live)\s+([\w\-]+)\s+(.+)\s*$/im
401
403
  session_name = $2
402
- opts = " #{$3}"
403
- env_vars = opts.scan(/\s+[\w\-]+="[^"]+"/i) + opts.scan(/\s+[\w\-]+='[^']+'/i)
404
+ if Thread.current[:command_orig].match(/\s*run\s+(repl|irb|live)\s+([\w\-]+)\s+(.+)\s*$/im)
405
+ opts = " #{$3}"
406
+ else
407
+ opts = ''
408
+ end
409
+ env_vars = opts.scan(/\s+[\w\-]+="[^"]+"/i) + opts.scan(/\s+[\w\-]+='[^']+'/i)
404
410
  opts.scan(/\s+[\w\-]+=[^'"\s]+/i).flatten.each do |ev|
405
411
  env_vars << ev.gsub('=',"='") + "'"
406
412
  end
@@ -409,12 +415,16 @@ class SlackSmartBot
409
415
  ev.lstrip!
410
416
  env_vars[idx] = "ENV['#{ev}"
411
417
  end
412
- run_repl(dest, user, session_name, env_vars.flatten, rules_file)
418
+ prerun = Thread.current[:command_orig].gsub('```', '`').scan(/\s+`(.+)`/m)
419
+ run_repl(dest, user, session_name, env_vars.flatten, prerun.flatten, rules_file)
413
420
  when /\A\s*(delete|remove)\s+(repl|irb|live)\s+([\w\-]+)\s*$/i
414
421
  repl_name = $3
415
422
  delete_repl(dest, user, repl_name)
416
423
  when /\A\s*see\s+(repls|repl|irb|irbs)\s*$/i
417
424
  see_repls(dest, user, typem)
425
+ when /\A\s*(kill)\s+(repl|irb|live)\s+([\w]+)\s*$/i
426
+ repl_id = $3
427
+ kill_repl(dest, user, repl_id)
418
428
  else
419
429
  processed2 = false
420
430
  end #of case
@@ -74,6 +74,12 @@ class SlackSmartBot
74
74
  if !dest.nil? and config.on_master_bot and !data.text.nil? and data.text.match(/^ping from (.+)\s*$/) and data.user == config[:nick_id]
75
75
  @pings << $1
76
76
  end
77
+ if config.on_master_bot and @vacations_check != Date.today
78
+ @vacations_check = Date.today
79
+ t = Thread.new do
80
+ check_vacations(only_first_day: true)
81
+ end
82
+ end
77
83
  typem = :dont_treat
78
84
  if data.nil? or data.user.nil? or data.user.to_s==''
79
85
  user_info = nil
@@ -186,7 +192,7 @@ class SlackSmartBot
186
192
  end
187
193
  end
188
194
  load "#{config.path}/rules/general_commands.rb" if File.exist?("#{config.path}/rules/general_commands.rb") and @datetime_general_commands != File.mtime("#{config.path}/rules/general_commands.rb")
189
-
195
+ eval(File.new(config.path + config.rules_file).read) if !defined?(rules) and File.exist?(config.path+config.rules_file) and !config.rules_file.empty?
190
196
  unless typem == :dont_treat or user_info.nil?
191
197
  if (Time.now - @last_activity_check) > 60 * 30 #every 30 minutes
192
198
  @last_activity_check = Time.now
@@ -350,6 +356,10 @@ class SlackSmartBot
350
356
  end
351
357
  end
352
358
  end
359
+ unless data.nil? or data.channel.nil? or data.channel.empty?
360
+ @announcements_activity_after[data.channel] ||= 0
361
+ @announcements_activity_after[data.channel] += 1
362
+ end
353
363
  rescue Exception => stack
354
364
  @logger.fatal stack
355
365
  end