slack-smart-bot 1.11.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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