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.
- checksums.yaml +4 -4
- data/README.md +44 -3
- data/lib/slack/smart-bot/comm/respond.rb +8 -2
- data/lib/slack/smart-bot/comm/set_status.rb +21 -0
- data/lib/slack/smart-bot/comm.rb +1 -0
- data/lib/slack/smart-bot/commands/general/add_memo_team.rb +117 -0
- data/lib/slack/smart-bot/commands/general/add_vacation.rb +51 -0
- data/lib/slack/smart-bot/commands/general/delete_memo_team.rb +69 -0
- data/lib/slack/smart-bot/commands/general/delete_team.rb +21 -1
- data/lib/slack/smart-bot/commands/general/remove_vacation.rb +27 -0
- data/lib/slack/smart-bot/commands/general/see_announcements.rb +1 -1
- data/lib/slack/smart-bot/commands/general/see_statuses.rb +1 -1
- data/lib/slack/smart-bot/commands/general/see_teams.rb +182 -32
- data/lib/slack/smart-bot/commands/general/see_vacations.rb +58 -0
- data/lib/slack/smart-bot/commands/general/see_vacations_team.rb +136 -0
- data/lib/slack/smart-bot/commands/general/set_memo_status.rb +58 -0
- data/lib/slack/smart-bot/commands/general/update_team.rb +22 -1
- data/lib/slack/smart-bot/commands/general_bot_commands.rb +172 -4
- data/lib/slack/smart-bot/commands/on_bot/general/bot_stats.rb +21 -5
- data/lib/slack/smart-bot/commands/on_bot/kill_repl.rb +32 -0
- data/lib/slack/smart-bot/commands/on_bot/repl.rb +1 -0
- data/lib/slack/smart-bot/commands/on_bot/run_repl.rb +113 -33
- data/lib/slack/smart-bot/commands/on_master/admin_master/publish_announcements.rb +3 -1
- data/lib/slack/smart-bot/commands.rb +8 -0
- data/lib/slack/smart-bot/process.rb +17 -7
- data/lib/slack/smart-bot/treat_message.rb +11 -1
- data/lib/slack/smart-bot/utils/check_vacations.rb +43 -0
- data/lib/slack/smart-bot/utils/get_admins_channels.rb +23 -3
- data/lib/slack/smart-bot/utils/get_command_ids.rb +1 -1
- data/lib/slack/smart-bot/utils/get_help.rb +4 -3
- data/lib/slack/smart-bot/utils/get_vacations.rb +22 -0
- data/lib/slack/smart-bot/utils/save_stats.rb +9 -2
- data/lib/slack/smart-bot/utils/save_status.rb +1 -1
- data/lib/slack/smart-bot/utils/update_admins_channels.rb +20 -3
- data/lib/slack/smart-bot/utils/update_vacations.rb +16 -0
- data/lib/slack/smart-bot/utils.rb +3 -0
- data/lib/slack-smart-bot.rb +22 -2
- data/whats_new.txt +12 -17
- 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+(
|
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
|
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
|
128
|
-
/\A\s*see\s+(red\s+|green\s+|white\s+|yellow\s
|
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.
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
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
|
@@ -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
|
-
|
25
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
54
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
77
|
-
|
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
|
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
|
400
|
-
/^\s*run\s+(repl|irb|live)\s+([\w\-]+)\s+(.+)\s*$/
|
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
|
-
|
403
|
-
|
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
|
-
|
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
|