lita-gsuite 0.5
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +134 -0
- data/lib/lita-gsuite.rb +15 -0
- data/lib/lita/commands/deletion_candidates.rb +49 -0
- data/lib/lita/commands/empty_groups.rb +36 -0
- data/lib/lita/commands/list_activities.rb +27 -0
- data/lib/lita/commands/list_admins.rb +39 -0
- data/lib/lita/commands/no_org_unit.rb +38 -0
- data/lib/lita/commands/suspension_candidates.rb +49 -0
- data/lib/lita/commands/two_factor_off.rb +49 -0
- data/lib/lita/commands/two_factor_stats.rb +56 -0
- data/lib/lita/google_activity.rb +50 -0
- data/lib/lita/google_group.rb +29 -0
- data/lib/lita/google_organisation_unit.rb +16 -0
- data/lib/lita/google_user.rb +69 -0
- data/lib/lita/gsuite.rb +373 -0
- data/lib/lita/gsuite_gateway.rb +101 -0
- data/lib/lita/redis_token_store.rb +48 -0
- data/lib/lita/weekly_schedule.rb +54 -0
- data/lib/lita/window_schedule.rb +50 -0
- metadata +166 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
|
3
|
+
module Lita
|
4
|
+
module Commands
|
5
|
+
class TwoFactorStats
|
6
|
+
|
7
|
+
def name
|
8
|
+
'two-factor-stats'
|
9
|
+
end
|
10
|
+
|
11
|
+
def run(robot, target, gateway, opts = {})
|
12
|
+
msg = build_msg(gateway)
|
13
|
+
robot.send_message(target, msg) if msg
|
14
|
+
robot.send_message(target, "No stats found") if msg.nil? && opts[:negative_ack]
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def build_msg(gateway)
|
20
|
+
users = active_users(gateway)
|
21
|
+
if users.any?
|
22
|
+
users_with_tfa = users.select { |user| user.two_factor_enabled? }
|
23
|
+
ou_paths = users.group_by(&:ou_path).keys.sort
|
24
|
+
|
25
|
+
msg = "Active users with Two Factor Authentication enabled:\n\n"
|
26
|
+
ou_paths.each do |ou_path|
|
27
|
+
msg += ou_msg(ou_path, users) + "\n"
|
28
|
+
end
|
29
|
+
msg + "- Overall #{users_with_tfa.size}/#{users.size} (#{percentage(users_with_tfa.size, users.size)}%)"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def ou_msg(ou_path, users)
|
34
|
+
ou_users = users.select { |user| user.ou_path == ou_path }
|
35
|
+
with_tfa = ou_users.select { |user| user.two_factor_enabled? }
|
36
|
+
"- #{ou_path} #{with_tfa.size}/#{ou_users.size} (#{percentage(with_tfa.size, ou_users.size)}%)"
|
37
|
+
end
|
38
|
+
|
39
|
+
def percentage(num, denom)
|
40
|
+
if denom == 0
|
41
|
+
result = BigDecimal.new(0)
|
42
|
+
else
|
43
|
+
result = BigDecimal.new(num) / BigDecimal.new(denom) * 100
|
44
|
+
end
|
45
|
+
result.round(2).to_s("F")
|
46
|
+
end
|
47
|
+
|
48
|
+
def active_users(gateway)
|
49
|
+
gateway.users.reject { |user|
|
50
|
+
user.suspended?
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Lita
|
2
|
+
class GoogleActivity
|
3
|
+
attr_reader :time, :actor, :ip, :name, :params
|
4
|
+
|
5
|
+
def self.from_api(item)
|
6
|
+
item.events.map { |event|
|
7
|
+
GoogleActivity.new(
|
8
|
+
time: item.id.time,
|
9
|
+
actor: item.actor.email,
|
10
|
+
ip: item.ip_address,
|
11
|
+
name: event.name,
|
12
|
+
params: event.parameters.inject({}) { |accum, param|
|
13
|
+
accum[param.name] = param.value
|
14
|
+
accum
|
15
|
+
}
|
16
|
+
)
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(time:, actor:, ip:, name:, params:)
|
21
|
+
@time = time
|
22
|
+
@actor = actor
|
23
|
+
@ip = ip
|
24
|
+
@name = name
|
25
|
+
@params = params
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
@actor
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_msg
|
33
|
+
<<~EOF
|
34
|
+
Date: #{@time.httpdate}
|
35
|
+
Admin User: #{@actor}
|
36
|
+
Action: #{@name.capitalize.gsub('_', ' ')}
|
37
|
+
#{values}
|
38
|
+
EOF
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def values
|
44
|
+
@params.map do |key, value|
|
45
|
+
"#{key.gsub('_', ' ')}: #{value}"
|
46
|
+
end.join("\n")
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Lita
|
2
|
+
# A google email group
|
3
|
+
class GoogleGroup
|
4
|
+
attr_reader :id, :email, :name, :member_count, :description
|
5
|
+
|
6
|
+
def self.from_api(group)
|
7
|
+
GoogleGroup.new(
|
8
|
+
id: group.id,
|
9
|
+
email: group.email,
|
10
|
+
name: group.name,
|
11
|
+
description: group.description,
|
12
|
+
member_count: group.direct_members_count,
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(id:, email:, name:, member_count:, description:)
|
17
|
+
@id, @email, @name, @description = id, email, name, description
|
18
|
+
@member_count = member_count.to_i
|
19
|
+
end
|
20
|
+
|
21
|
+
def ==(other)
|
22
|
+
@id == other.id && @email == other.email
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
@email
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Lita
|
2
|
+
# A google OrganisationUnit, contains zero or more users
|
3
|
+
class GoogleOrganisationUnit
|
4
|
+
attr_reader :name, :path
|
5
|
+
|
6
|
+
def self.from_api(item)
|
7
|
+
GoogleOrganisationUnit.new(
|
8
|
+
name: item.name, path: item.org_unit_path
|
9
|
+
)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(name:, path:)
|
13
|
+
@name, @path = name, path
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Lita
|
2
|
+
# A google apps user
|
3
|
+
class GoogleUser
|
4
|
+
attr_reader :id, :full_name, :email, :created_at, :last_login_at, :ou_path
|
5
|
+
|
6
|
+
def self.from_api_user(user)
|
7
|
+
GoogleUser.new(
|
8
|
+
id: user.id,
|
9
|
+
full_name: user.name.full_name,
|
10
|
+
email: user.primary_email,
|
11
|
+
suspended: user.suspended,
|
12
|
+
created_at: user.creation_time,
|
13
|
+
last_login_at: user.last_login_time,
|
14
|
+
ou_path: user.org_unit_path,
|
15
|
+
admin: user.is_admin,
|
16
|
+
delegated_admin: user.is_delegated_admin,
|
17
|
+
two_factor_enabled: user.is_enrolled_in2_sv,
|
18
|
+
two_factor_enforced: user.is_enforced_in2_sv,
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(id:, full_name:, email:, suspended:, created_at:, last_login_at:, ou_path:, admin:, delegated_admin:, two_factor_enabled:, two_factor_enforced:)
|
23
|
+
@id = id
|
24
|
+
@email = email
|
25
|
+
@full_name = full_name
|
26
|
+
@suspended = suspended
|
27
|
+
@created_at = created_at
|
28
|
+
@last_login_at = last_login_at
|
29
|
+
@ou_path = ou_path
|
30
|
+
@admin = admin
|
31
|
+
@delegated_admin = delegated_admin
|
32
|
+
@two_factor_enabled = two_factor_enabled
|
33
|
+
@two_factor_enforced = two_factor_enforced
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
@email
|
38
|
+
end
|
39
|
+
|
40
|
+
def admin?
|
41
|
+
@admin
|
42
|
+
end
|
43
|
+
|
44
|
+
def delegated_admin?
|
45
|
+
@delegated_admin
|
46
|
+
end
|
47
|
+
|
48
|
+
def path
|
49
|
+
if @ou_path.end_with?("/")
|
50
|
+
"#{@ou_path}#{@email}"
|
51
|
+
else
|
52
|
+
"#{@ou_path}/#{@email}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def suspended?
|
57
|
+
@suspended
|
58
|
+
end
|
59
|
+
|
60
|
+
def two_factor_enabled?
|
61
|
+
@two_factor_enabled
|
62
|
+
end
|
63
|
+
|
64
|
+
def two_factor_enforced?
|
65
|
+
@two_factor_enforced
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
data/lib/lita/gsuite.rb
ADDED
@@ -0,0 +1,373 @@
|
|
1
|
+
require "lita"
|
2
|
+
require "lita-timing"
|
3
|
+
require 'googleauth'
|
4
|
+
require 'securerandom'
|
5
|
+
|
6
|
+
module Lita
|
7
|
+
class Gsuite < Handler
|
8
|
+
TIMER_INTERVAL = 60
|
9
|
+
OOB_OAUTH_URI = 'urn:ietf:wg:oauth:2.0:oob'
|
10
|
+
|
11
|
+
COMMANDS = [
|
12
|
+
Commands::ListAdmins,
|
13
|
+
Commands::EmptyGroups,
|
14
|
+
Commands::NoOrgUnit,
|
15
|
+
Commands::TwoFactorOff,
|
16
|
+
Commands::TwoFactorStats,
|
17
|
+
Commands::SuspensionCandidates,
|
18
|
+
Commands::DeletionCandidates,
|
19
|
+
].map { |cmd|
|
20
|
+
[cmd.new.name, cmd]
|
21
|
+
}.to_h
|
22
|
+
|
23
|
+
WINDOW_COMMANDS = [
|
24
|
+
Commands::ListActivities
|
25
|
+
].map { |cmd|
|
26
|
+
[cmd.new.name, cmd]
|
27
|
+
}.to_h
|
28
|
+
|
29
|
+
config :oauth_client_id
|
30
|
+
config :oauth_client_secret
|
31
|
+
|
32
|
+
# Authentication commands - each user is required to run these before they can interact with
|
33
|
+
# the Google API
|
34
|
+
route(/^gsuite auth$/, :start_auth, command: true, help: {"gsuite auth" => "Initiate the first of two steps required to authorise the current user wth Google"})
|
35
|
+
route(/^gsuite set-token (.+)$/, :set_token, command: true, help: {"gsuite set-token <token>" => "The second and final step required to authorise the current user with Google. Run 'gsuite auth' first"})
|
36
|
+
|
37
|
+
# Instant queries. Authenticated users can run these commands and the result will be returned
|
38
|
+
# immediately
|
39
|
+
route(/^gsuite list-admins$/, :list_admins, command: true, help: {"gsuite list-admins" => "List active admins"})
|
40
|
+
route(/^gsuite suspension-candidates$/, :suspension_candidates, command: true, help: {"gsuite suspension-candidates" => "List active users that habven't signed in for a while"})
|
41
|
+
route(/^gsuite deletion-candidates$/, :deletion_candidates, command: true, help: {"gsuite deletion-candidates" => "List suspended users that habven't signed in for a while"})
|
42
|
+
route(/^gsuite no-ou$/, :no_org_unit, command: true, help: {"gsuite no-ou" => "List users that aren't assigned to an Organisation Unit"})
|
43
|
+
route(/^gsuite empty-groups$/, :empty_groups, command: true, help: {"gsuite empty-groups" => "List groups with no users"})
|
44
|
+
route(/^gsuite two-factor-stats$/, :two_factor_stats, command: true, help: {"gsuite two-factor-stats" => "Display stats on option of two factor authentication"})
|
45
|
+
route(/^gsuite two-factor-off (.+)$/, :two_factor_off, command: true, help: {"gsuite two-factor-off <OU path>" => "List users from the OU path with two factor authentication off"})
|
46
|
+
|
47
|
+
# Control a schedule of automated commands to run in specific channels
|
48
|
+
route(/^gsuite schedule list$/, :schedule_list, command: true, help: {"gsuite schedule list" => "Print the list of scheduled gsuite commands for the current channel"})
|
49
|
+
route(/^gsuite schedule commands$/, :schedule_commands, command: true, help: {"gsuite schedule commands" => "Print the list of commands available for scheduling"})
|
50
|
+
route(/^gsuite schedule add-weekly (.+) (\d\d:\d\d) (.+)$/, :schedule_add_weekly, command: true, help: {"gsuite schedule add-weekly <day> <HH:MM> <cmd>" => "Add a new weekly scheduled command. Run 'gsuite schedule commands' to see the available commands"})
|
51
|
+
route(/^gsuite schedule add-window (.+)$/, :schedule_add_window, command: true, help: {"gsuite schedule add-window <cmd>" => "Add a new scheduled window command"})
|
52
|
+
route(/^gsuite schedule del (.+)$/, :schedule_delete, command: true, help: {"gsuite schedule del <cmd-id>" => "Delete a scheduled command. Requires a command ID, which is printed in 'gsuite schedule list' output"})
|
53
|
+
|
54
|
+
on :loaded, :start_timers
|
55
|
+
|
56
|
+
def start_timers(payload)
|
57
|
+
weekly_commands_timer
|
58
|
+
window_commands_timer
|
59
|
+
end
|
60
|
+
|
61
|
+
def deletion_candidates(response)
|
62
|
+
return unless confirm_user_authenticated(response)
|
63
|
+
|
64
|
+
Commands::DeletionCandidates.new.run(
|
65
|
+
robot,
|
66
|
+
Source.new(room: response.room),
|
67
|
+
gateway(response.user),
|
68
|
+
negative_ack: true
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
def empty_groups(response)
|
73
|
+
return unless confirm_user_authenticated(response)
|
74
|
+
|
75
|
+
Commands::EmptyGroups.new.run(
|
76
|
+
robot,
|
77
|
+
Source.new(room: response.room),
|
78
|
+
gateway(response.user),
|
79
|
+
negative_ack: true
|
80
|
+
)
|
81
|
+
end
|
82
|
+
|
83
|
+
def list_admins(response)
|
84
|
+
return unless confirm_user_authenticated(response)
|
85
|
+
|
86
|
+
Commands::ListAdmins.new.run(
|
87
|
+
robot,
|
88
|
+
Source.new(room: response.room),
|
89
|
+
gateway(response.user),
|
90
|
+
negative_ack: true
|
91
|
+
)
|
92
|
+
end
|
93
|
+
|
94
|
+
def no_org_unit(response)
|
95
|
+
return unless confirm_user_authenticated(response)
|
96
|
+
|
97
|
+
Commands::NoOrgUnit.new.run(
|
98
|
+
robot,
|
99
|
+
Source.new(room: response.room),
|
100
|
+
gateway(response.user),
|
101
|
+
negative_ack: true
|
102
|
+
)
|
103
|
+
end
|
104
|
+
|
105
|
+
def suspension_candidates(response)
|
106
|
+
return unless confirm_user_authenticated(response)
|
107
|
+
|
108
|
+
Commands::SuspensionCandidates.new.run(
|
109
|
+
robot,
|
110
|
+
Source.new(room: response.room),
|
111
|
+
gateway(response.user),
|
112
|
+
negative_ack: true
|
113
|
+
)
|
114
|
+
end
|
115
|
+
|
116
|
+
def two_factor_off(response)
|
117
|
+
return unless confirm_user_authenticated(response)
|
118
|
+
|
119
|
+
ou_path = response.match_data[1].to_s
|
120
|
+
Commands::TwoFactorOff.new(ou_path).run(
|
121
|
+
robot,
|
122
|
+
Source.new(room: response.room),
|
123
|
+
gateway(response.user),
|
124
|
+
negative_ack: true
|
125
|
+
)
|
126
|
+
end
|
127
|
+
|
128
|
+
def two_factor_stats(response)
|
129
|
+
return unless confirm_user_authenticated(response)
|
130
|
+
|
131
|
+
Commands::TwoFactorStats.new.run(
|
132
|
+
robot,
|
133
|
+
Source.new(room: response.room),
|
134
|
+
gateway(response.user),
|
135
|
+
negative_ack: true
|
136
|
+
)
|
137
|
+
end
|
138
|
+
|
139
|
+
def start_auth(response)
|
140
|
+
credentials = google_credentials_for_user(response.user)
|
141
|
+
url = google_authorizer.get_authorization_url(base_url: OOB_OAUTH_URI)
|
142
|
+
if credentials.nil?
|
143
|
+
response.reply "Open the following URL in your browser and enter the resulting code via the 'gsuite set-token <foo>' command:\n\n#{url}"
|
144
|
+
else
|
145
|
+
response.reply "#{response.user.name} is already authorized with Google. To re-authorize, open the following URL in your browser and enter the resulting code via the 'gsuite set-token <foo>' command:\n\n#{url}"
|
146
|
+
end
|
147
|
+
rescue StandardError => e
|
148
|
+
response.reply("Error: #{e.class} #{e.message}")
|
149
|
+
end
|
150
|
+
|
151
|
+
def set_token(response)
|
152
|
+
auth_code = response.match_data[1].to_s
|
153
|
+
|
154
|
+
google_authorizer.get_and_store_credentials_from_code(user_id: response.user.id, code: auth_code, base_url: OOB_OAUTH_URI)
|
155
|
+
response.reply("#{response.user.name} now authorized")
|
156
|
+
rescue StandardError => e
|
157
|
+
response.reply("Error: #{e.class} #{e.message}")
|
158
|
+
end
|
159
|
+
|
160
|
+
def schedule_list(response)
|
161
|
+
room_commands = (weekly_commands_for_room(response.room.name) + window_commands_for_room(response.room.name)).select { |cmd|
|
162
|
+
cmd.room_id == response.room.id
|
163
|
+
}
|
164
|
+
if room_commands.any?
|
165
|
+
room_commands.each do |cmd|
|
166
|
+
response.reply("#{cmd.human}")
|
167
|
+
end
|
168
|
+
else
|
169
|
+
response.reply("no scheduled commands for this room")
|
170
|
+
end
|
171
|
+
rescue StandardError => e
|
172
|
+
response.reply("Error: #{e.class} #{e.message}")
|
173
|
+
end
|
174
|
+
|
175
|
+
def schedule_commands(response)
|
176
|
+
msg = "The following commands are available for scheduling weekly:\n\n"
|
177
|
+
COMMANDS.each do |cmd_name, cmd_klass|
|
178
|
+
msg += "- #{cmd_name}\n"
|
179
|
+
end
|
180
|
+
msg += "The following commands are available for scheduling for sliding windows:\n\n"
|
181
|
+
WINDOW_COMMANDS.each do |cmd_name, cmd_klass|
|
182
|
+
msg += "- #{cmd_name}\n"
|
183
|
+
end
|
184
|
+
response.reply(msg)
|
185
|
+
rescue StandardError => e
|
186
|
+
response.reply("Error: #{e.class} #{e.message}")
|
187
|
+
end
|
188
|
+
|
189
|
+
def schedule_add_weekly(response)
|
190
|
+
return unless confirm_user_authenticated(response)
|
191
|
+
|
192
|
+
_, day, time, cmd_name = *response.match_data
|
193
|
+
|
194
|
+
schedule = WeeklySchedule.new(
|
195
|
+
id: SecureRandom.hex(3),
|
196
|
+
day: day,
|
197
|
+
time: time,
|
198
|
+
cmd: COMMANDS.fetch(cmd_name.downcase, nil),
|
199
|
+
user_id: response.user.id,
|
200
|
+
room_id: response.room.id,
|
201
|
+
)
|
202
|
+
if schedule.valid?
|
203
|
+
redis.hmset("weekly-schedule", schedule.id, schedule.to_json)
|
204
|
+
response.reply("scheduled command")
|
205
|
+
else
|
206
|
+
response.reply("invalid command")
|
207
|
+
end
|
208
|
+
rescue StandardError => e
|
209
|
+
response.reply("Error: #{e.class} #{e.message}")
|
210
|
+
end
|
211
|
+
|
212
|
+
def schedule_add_window(response)
|
213
|
+
return unless confirm_user_authenticated(response)
|
214
|
+
|
215
|
+
cmd_name = response.match_data[1].to_s
|
216
|
+
schedule = WindowSchedule.new(
|
217
|
+
id: SecureRandom.hex(3),
|
218
|
+
cmd: WINDOW_COMMANDS.fetch(cmd_name.downcase, nil),
|
219
|
+
user_id: response.user.id,
|
220
|
+
room_id: response.room.id,
|
221
|
+
)
|
222
|
+
if schedule.valid?
|
223
|
+
redis.hmset("window-schedule", schedule.id, schedule.to_json)
|
224
|
+
response.reply("scheduled command")
|
225
|
+
else
|
226
|
+
response.reply("invalid command")
|
227
|
+
end
|
228
|
+
rescue StandardError => e
|
229
|
+
response.reply("Error: #{e.class} #{e.message}")
|
230
|
+
end
|
231
|
+
|
232
|
+
def schedule_delete(response)
|
233
|
+
cmd_id = response.match_data[1].to_s
|
234
|
+
|
235
|
+
count = redis.hdel("weekly-schedule", cmd_id)
|
236
|
+
count += redis.hdel("window-schedule", cmd_id)
|
237
|
+
if count > 0
|
238
|
+
response.reply("scheduled command #{cmd_id} deleted")
|
239
|
+
else
|
240
|
+
response.reply("no scheduled command with ID #{cmd_id} found")
|
241
|
+
end
|
242
|
+
rescue StandardError => e
|
243
|
+
response.reply("Error: #{e.class} #{e.message}")
|
244
|
+
end
|
245
|
+
|
246
|
+
private
|
247
|
+
|
248
|
+
def confirm_user_authenticated(response)
|
249
|
+
credentials = google_credentials_for_user(response.user)
|
250
|
+
if credentials.nil?
|
251
|
+
response.reply("#{response.user.name} not authorized with Google yet. Use the 'gsuite auth' command to initiate authorization")
|
252
|
+
false
|
253
|
+
else
|
254
|
+
true
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def google_credentials_for_user(user)
|
259
|
+
google_authorizer.get_credentials(user.id)
|
260
|
+
end
|
261
|
+
|
262
|
+
def google_authorizer
|
263
|
+
@google_authorizer ||= begin
|
264
|
+
client_id = Google::Auth::ClientId.new(
|
265
|
+
config.oauth_client_id,
|
266
|
+
config.oauth_client_secret
|
267
|
+
)
|
268
|
+
token_store = RedisTokenStore.new(redis)
|
269
|
+
Google::Auth::UserAuthorizer.new(client_id, GsuiteGateway::OAUTH_SCOPES, token_store)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def weekly_commands_timer
|
274
|
+
every_with_logged_errors(TIMER_INTERVAL) do |timer|
|
275
|
+
weekly_commands.each do |cmd|
|
276
|
+
weekly_at(cmd.time, cmd.day, "#{cmd.id}-#{cmd.name}") do
|
277
|
+
target = Source.new(
|
278
|
+
room: Lita::Room.create_or_update(cmd.room_id)
|
279
|
+
)
|
280
|
+
user = Lita::User.find_by_id(cmd.user_id)
|
281
|
+
cmd.run(robot, target, gateway(user))
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def window_commands_timer
|
288
|
+
every_with_logged_errors(TIMER_INTERVAL) do |timer|
|
289
|
+
window_commands.each do |cmd|
|
290
|
+
target = Source.new(
|
291
|
+
room: Lita::Room.create_or_update(cmd.room_id)
|
292
|
+
)
|
293
|
+
user = Lita::User.find_by_id(cmd.user_id)
|
294
|
+
sliding_window ||= Lita::Timing::SlidingWindow.new("#{cmd.id}-#{cmd.name}", redis)
|
295
|
+
sliding_window.advance(duration_minutes: cmd.duration_minutes, buffer_minutes: cmd.buffer_minutes) do |window_start, window_end|
|
296
|
+
cmd.run(robot, target, gateway(user), window_start, window_end)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def weekly_commands_for_room(room_id)
|
303
|
+
weekly_commands.select { |cmd|
|
304
|
+
cmd.room_id == room_id
|
305
|
+
}
|
306
|
+
end
|
307
|
+
|
308
|
+
def window_commands_for_room(room_id)
|
309
|
+
window_commands.select { |cmd|
|
310
|
+
cmd.room_id == room_id
|
311
|
+
}
|
312
|
+
end
|
313
|
+
|
314
|
+
def weekly_commands
|
315
|
+
redis.hgetall("weekly-schedule").map { |_id, data|
|
316
|
+
JSON.parse(data)
|
317
|
+
}.map { |data|
|
318
|
+
WeeklySchedule.new(
|
319
|
+
id: data.fetch("id", "foo"),
|
320
|
+
day: data.fetch("day", nil),
|
321
|
+
time: data.fetch("time", "12:00"),
|
322
|
+
room_id: data.fetch("room_id", nil),
|
323
|
+
user_id: data.fetch("user_id", nil),
|
324
|
+
cmd: COMMANDS.fetch(data.fetch("cmd", nil), nil),
|
325
|
+
)
|
326
|
+
}.select { |schedule|
|
327
|
+
schedule.valid?
|
328
|
+
}
|
329
|
+
end
|
330
|
+
|
331
|
+
def window_commands
|
332
|
+
redis.hgetall("window-schedule").map { |_id, data|
|
333
|
+
JSON.parse(data)
|
334
|
+
}.map { |data|
|
335
|
+
WindowSchedule.new(
|
336
|
+
id: data.fetch("id", nil),
|
337
|
+
room_id: data.fetch("room_id", nil),
|
338
|
+
user_id: data.fetch("user_id", nil),
|
339
|
+
cmd: WINDOW_COMMANDS.fetch(data.fetch("cmd", nil), nil),
|
340
|
+
)
|
341
|
+
}.select { |schedule|
|
342
|
+
schedule.valid?
|
343
|
+
}
|
344
|
+
end
|
345
|
+
|
346
|
+
def every_with_logged_errors(interval, &block)
|
347
|
+
every(interval) do
|
348
|
+
logged_errors do
|
349
|
+
yield
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
def logged_errors(&block)
|
355
|
+
yield
|
356
|
+
rescue StandardError => e
|
357
|
+
$stderr.puts "Error in timer loop: #{e.class} #{e.message} #{e.backtrace.first}"
|
358
|
+
end
|
359
|
+
|
360
|
+
def weekly_at(time, day, name, &block)
|
361
|
+
Lita::Timing::Scheduled.new(name, redis).weekly_at(time, day, &block)
|
362
|
+
end
|
363
|
+
|
364
|
+
def gateway(user)
|
365
|
+
Lita::GsuiteGateway.new(
|
366
|
+
user_authorization: google_authorizer.get_credentials(user.id)
|
367
|
+
)
|
368
|
+
end
|
369
|
+
|
370
|
+
Lita.register_handler(self)
|
371
|
+
|
372
|
+
end
|
373
|
+
end
|