calendar-assistant 0.2.1 → 0.3.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/Gemfile +1 -0
- data/README.md +280 -202
- data/Rakefile +9 -1
- data/lib/calendar_assistant.rb +35 -187
- data/lib/calendar_assistant/authorizer.rb +0 -3
- data/lib/calendar_assistant/calendar_assistant.rb +122 -0
- data/lib/calendar_assistant/cli.rb +84 -54
- data/lib/calendar_assistant/cli_helpers.rb +101 -38
- data/lib/calendar_assistant/config.rb +71 -12
- data/lib/calendar_assistant/date_helpers.rb +16 -0
- data/lib/calendar_assistant/event.rb +124 -14
- data/lib/calendar_assistant/event_repository.rb +30 -19
- data/lib/calendar_assistant/event_repository_factory.rb +7 -0
- data/lib/calendar_assistant/event_set.rb +124 -0
- data/lib/calendar_assistant/extensions/{event_date_time_extensions.rb → google_apis_extensions.rb} +5 -6
- data/lib/calendar_assistant/local_service.rb +82 -0
- data/lib/calendar_assistant/scheduler.rb +45 -0
- data/lib/calendar_assistant/string_helpers.rb +0 -2
- data/lib/calendar_assistant/version.rb +1 -1
- metadata +9 -4
- data/lib/calendar_assistant/extensions/event_extensions.rb +0 -71
@@ -20,28 +20,30 @@ class CalendarAssistant
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def self.now
|
23
|
-
CalendarAssistant::Event.new(
|
24
|
-
|
25
|
-
|
23
|
+
CalendarAssistant::Event.new(
|
24
|
+
Google::Apis::CalendarV3::Event.new(start: Google::Apis::CalendarV3::EventDateTime.new(date_time: Time.now),
|
25
|
+
end: Google::Apis::CalendarV3::EventDateTime.new(date_time: Time.now),
|
26
|
+
summary: Rainbow(" now ").inverse.faint)
|
27
|
+
)
|
26
28
|
end
|
27
29
|
|
28
30
|
def self.find_av_uri ca, timespec
|
29
31
|
time = Chronic.parse timespec
|
30
32
|
range = time..(time+5.minutes)
|
31
|
-
|
33
|
+
event_set = ca.find_events range
|
32
34
|
|
33
|
-
[
|
34
|
-
|
35
|
-
|
35
|
+
[CalendarAssistant::Event::Response::ACCEPTED,
|
36
|
+
CalendarAssistant::Event::Response::TENTATIVE,
|
37
|
+
CalendarAssistant::Event::Response::NEEDS_ACTION,
|
36
38
|
].each do |response|
|
37
|
-
events.reverse.select do |event|
|
39
|
+
event_set.events.reverse.select do |event|
|
38
40
|
event.response_status == response
|
39
41
|
end.each do |event|
|
40
|
-
return [event, event.av_uri] if event.av_uri
|
42
|
+
return [event_set.new(event), event.av_uri] if event.av_uri
|
41
43
|
end
|
42
44
|
end
|
43
45
|
|
44
|
-
nil
|
46
|
+
event_set.new(nil)
|
45
47
|
end
|
46
48
|
|
47
49
|
class Out
|
@@ -80,78 +82,139 @@ class CalendarAssistant
|
|
80
82
|
return false if event.start_date != Date.today
|
81
83
|
|
82
84
|
if event.start_time > Time.now
|
83
|
-
puts
|
85
|
+
puts event_description(CLIHelpers.now)
|
84
86
|
return true
|
85
87
|
end
|
86
88
|
|
87
89
|
false
|
88
90
|
end
|
89
91
|
|
90
|
-
def print_events ca,
|
91
|
-
unless
|
92
|
-
|
93
|
-
|
92
|
+
def print_events ca, event_set, omit_title: false
|
93
|
+
unless omit_title
|
94
|
+
er = event_set.event_repository
|
95
|
+
puts Rainbow("#{er.calendar.id} (all times in #{er.calendar.time_zone})\n").italic
|
94
96
|
end
|
95
97
|
|
96
|
-
if
|
97
|
-
events.each do |key, value|
|
98
|
+
if event_set.is_a?(EventSet::Hash)
|
99
|
+
event_set.events.each do |key, value|
|
98
100
|
puts Rainbow(key.to_s.capitalize + ":").bold.italic
|
99
|
-
print_events ca, value,
|
101
|
+
print_events ca, event_set.new(value), omit_title: true
|
100
102
|
end
|
101
103
|
return
|
102
104
|
end
|
103
105
|
|
104
|
-
events = Array(events)
|
106
|
+
events = Array(event_set.events)
|
105
107
|
if events.empty?
|
106
108
|
puts "No events in this time range."
|
107
109
|
return
|
108
110
|
end
|
109
111
|
|
110
112
|
display_events = events.select do |event|
|
111
|
-
!
|
113
|
+
! ca.config.setting(CalendarAssistant::Config::Keys::Options::COMMITMENTS) || event.commitment?
|
112
114
|
end
|
113
115
|
|
114
116
|
printed_now = false
|
115
117
|
display_events.each do |event|
|
116
118
|
printed_now = print_now! ca, event, printed_now
|
117
|
-
puts
|
118
|
-
pp event if
|
119
|
+
puts event_description(event)
|
120
|
+
pp event if ca.config.debug?
|
119
121
|
end
|
120
122
|
|
121
123
|
puts
|
122
124
|
end
|
123
125
|
|
124
|
-
def print_available_blocks ca,
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
126
|
+
def print_available_blocks ca, event_set, omit_title: false
|
127
|
+
ers = ca.config.attendees.map { |calendar_id| ca.event_repository calendar_id }
|
128
|
+
time_zones = ers.map { |er| er.calendar.time_zone }.uniq
|
129
|
+
|
130
|
+
unless omit_title
|
131
|
+
puts Rainbow(ers.map { |er| er.calendar.id }.join(", ")).italic
|
132
|
+
puts Rainbow(sprintf("- looking for blocks at least %s long",
|
133
|
+
ChronicDuration.output(
|
134
|
+
ChronicDuration.parse(
|
135
|
+
ca.config.setting(Config::Keys::Settings::MEETING_LENGTH))))).italic
|
136
|
+
time_zones.each do |time_zone|
|
137
|
+
puts Rainbow(sprintf("- between %s and %s in %s",
|
138
|
+
ca.config.setting(Config::Keys::Settings::START_OF_DAY),
|
139
|
+
ca.config.setting(Config::Keys::Settings::END_OF_DAY),
|
140
|
+
time_zone,
|
141
|
+
)).italic
|
142
|
+
end
|
143
|
+
puts
|
132
144
|
end
|
133
145
|
|
134
|
-
if
|
135
|
-
events.each do |key, value|
|
146
|
+
if event_set.is_a?(EventSet::Hash)
|
147
|
+
event_set.events.each do |key, value|
|
136
148
|
puts(sprintf(Rainbow("Availability on %s:\n").bold,
|
137
149
|
key.strftime("%A, %B %-d")))
|
138
|
-
print_available_blocks ca, value,
|
150
|
+
print_available_blocks ca, event_set.new(value), omit_title: true
|
139
151
|
puts
|
140
152
|
end
|
141
153
|
return
|
142
154
|
end
|
143
155
|
|
144
|
-
events = Array(events)
|
156
|
+
events = Array(event_set.events)
|
145
157
|
if events.empty?
|
146
158
|
puts " (No available blocks in this time range.)"
|
147
159
|
return
|
148
160
|
end
|
149
161
|
|
150
162
|
events.each do |event|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
163
|
+
line = []
|
164
|
+
time_zones.each do |time_zone|
|
165
|
+
line << sprintf("%s - %s",
|
166
|
+
event.start_time.in_time_zone(time_zone).strftime("%l:%M%P"),
|
167
|
+
event.end_time.in_time_zone(time_zone).strftime("%l:%M%P %Z"))
|
168
|
+
end
|
169
|
+
line.uniq!
|
170
|
+
puts " • " + line.join(" / ") + Rainbow(" (" + event.duration + ")").italic
|
171
|
+
pp event if ca.config.debug?
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def event_description event
|
176
|
+
s = sprintf("%-25.25s", event_date_description(event))
|
177
|
+
|
178
|
+
date_ansi_codes = []
|
179
|
+
date_ansi_codes << :bright if event.current?
|
180
|
+
date_ansi_codes << :faint if event.past?
|
181
|
+
s = date_ansi_codes.inject(Rainbow(s)) { |text, ansi| text.send ansi }
|
182
|
+
|
183
|
+
s += Rainbow(sprintf(" | %s", event.view_summary)).bold
|
184
|
+
|
185
|
+
attributes = []
|
186
|
+
unless event.private?
|
187
|
+
attributes << "recurring" if event.recurring?
|
188
|
+
attributes << "not-busy" unless event.busy?
|
189
|
+
attributes << "self" if event.self? && !event.private?
|
190
|
+
attributes << "1:1" if event.one_on_one?
|
191
|
+
attributes << "awaiting" if event.awaiting?
|
192
|
+
end
|
193
|
+
|
194
|
+
attributes << event.visibility if event.explicit_visibility?
|
195
|
+
|
196
|
+
s += Rainbow(sprintf(" (%s)", attributes.to_a.sort.join(", "))).italic unless attributes.empty?
|
197
|
+
|
198
|
+
s = Rainbow(Rainbow.uncolor(s)).faint.strike if event.declined?
|
199
|
+
|
200
|
+
s
|
201
|
+
end
|
202
|
+
|
203
|
+
def event_date_description event
|
204
|
+
if event.all_day?
|
205
|
+
start_date = event.start_date
|
206
|
+
end_date = event.end_date
|
207
|
+
if (end_date - start_date) <= 1
|
208
|
+
event.start.to_s
|
209
|
+
else
|
210
|
+
sprintf("%s - %s", start_date, end_date - 1.day)
|
211
|
+
end
|
212
|
+
else
|
213
|
+
if event.start_date == event.end_date
|
214
|
+
sprintf("%s - %s", event.start.date_time.strftime("%Y-%m-%d %H:%M"), event.end.date_time.strftime("%H:%M"))
|
215
|
+
else
|
216
|
+
sprintf("%s - %s", event.start.date_time.strftime("%Y-%m-%d %H:%M"), event.end.date_time.strftime("%Y-%m-%d %H:%M"))
|
217
|
+
end
|
155
218
|
end
|
156
219
|
end
|
157
220
|
end
|
@@ -1,30 +1,48 @@
|
|
1
|
-
require "toml"
|
2
|
-
|
3
1
|
class CalendarAssistant
|
4
2
|
class Config
|
3
|
+
autoload :TokenStore, "calendar_assistant/config/token_store"
|
4
|
+
|
5
5
|
class TomlParseFailure < CalendarAssistant::BaseException ; end
|
6
6
|
class NoConfigFileToPersist < CalendarAssistant::BaseException ; end
|
7
7
|
class NoTokensAuthorized < CalendarAssistant::BaseException ; end
|
8
8
|
class AccessingHashAsScalar < CalendarAssistant::BaseException ; end
|
9
9
|
|
10
10
|
CONFIG_FILE_PATH = File.join ENV["HOME"], ".calendar-assistant"
|
11
|
+
DEFAULT_CALENDAR_ID = "primary"
|
11
12
|
|
12
13
|
module Keys
|
13
14
|
TOKENS = "tokens"
|
14
15
|
SETTINGS = "settings"
|
15
16
|
|
17
|
+
#
|
18
|
+
# Settings are values that have a value in DEFAULT_SETTINGS below,
|
19
|
+
# and which can be overridden by entries in the user config file
|
20
|
+
#
|
16
21
|
module Settings
|
17
|
-
PROFILE = "profile"
|
18
|
-
MEETING_LENGTH = "meeting-length"
|
19
|
-
START_OF_DAY = "start-of-day"
|
20
|
-
END_OF_DAY = "end-of-day"
|
22
|
+
PROFILE = "profile" # string
|
23
|
+
MEETING_LENGTH = "meeting-length" # ChronicDuration
|
24
|
+
START_OF_DAY = "start-of-day" # BusinessTime
|
25
|
+
END_OF_DAY = "end-of-day" # BusinessTime
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
# Options are ephemeral command-line flag settings which _may_
|
30
|
+
# have a value in DEFAULT_SETTINGS below
|
31
|
+
#
|
32
|
+
module Options
|
33
|
+
COMMITMENTS = "commitments" # bool
|
34
|
+
JOIN = "join" # bool
|
35
|
+
ATTENDEES = "attendees" # array of calendar ids (comma-delimited)
|
36
|
+
LOCAL_STORE = "local-store" # filename
|
37
|
+
DEBUG = "debug" # bool
|
21
38
|
end
|
22
39
|
end
|
23
40
|
|
24
41
|
DEFAULT_SETTINGS = {
|
25
|
-
Keys::Settings::MEETING_LENGTH => "30m",
|
26
|
-
Keys::Settings::START_OF_DAY => "9am",
|
27
|
-
Keys::Settings::END_OF_DAY => "6pm",
|
42
|
+
Keys::Settings::MEETING_LENGTH => "30m", # ChronicDuration
|
43
|
+
Keys::Settings::START_OF_DAY => "9am", # BusinessTime
|
44
|
+
Keys::Settings::END_OF_DAY => "6pm", # BusinessTime
|
45
|
+
Keys::Options::ATTENDEES => [DEFAULT_CALENDAR_ID], # array of calendar ids
|
28
46
|
}
|
29
47
|
|
30
48
|
attr_reader :config_file_path, :user_config, :options, :defaults
|
@@ -58,9 +76,23 @@ class CalendarAssistant
|
|
58
76
|
@options = options
|
59
77
|
end
|
60
78
|
|
79
|
+
def in_env &block
|
80
|
+
# this is totally not thread-safe
|
81
|
+
orig_b_o_d = BusinessTime::Config.beginning_of_workday
|
82
|
+
orig_e_o_d = BusinessTime::Config.end_of_workday
|
83
|
+
begin
|
84
|
+
BusinessTime::Config.beginning_of_workday = setting(Config::Keys::Settings::START_OF_DAY)
|
85
|
+
BusinessTime::Config.end_of_workday = setting(Config::Keys::Settings::END_OF_DAY)
|
86
|
+
yield
|
87
|
+
ensure
|
88
|
+
BusinessTime::Config.beginning_of_workday = orig_b_o_d
|
89
|
+
BusinessTime::Config.end_of_workday = orig_e_o_d
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
61
93
|
def profile_name
|
62
94
|
# CLI option takes precedence
|
63
|
-
return options[
|
95
|
+
return options[Keys::Settings::PROFILE] if options[Keys::Settings::PROFILE]
|
64
96
|
|
65
97
|
# then a configured preference takes precedence
|
66
98
|
default = get([Keys::SETTINGS, Keys::Settings::PROFILE])
|
@@ -91,12 +123,26 @@ class CalendarAssistant
|
|
91
123
|
Config.set_in_hash user_config, keypath, value
|
92
124
|
end
|
93
125
|
|
126
|
+
#
|
127
|
+
# note that, despite the name, this method returns both options
|
128
|
+
# and settings
|
129
|
+
#
|
94
130
|
def setting setting_name
|
95
131
|
Config.find_in_hash(options, setting_name) ||
|
96
132
|
Config.find_in_hash(user_config, [Keys::SETTINGS, setting_name]) ||
|
97
133
|
Config.find_in_hash(defaults, setting_name)
|
98
134
|
end
|
99
135
|
|
136
|
+
def settings
|
137
|
+
setting_names = CalendarAssistant::Config::Keys::Settings.constants.map do |k|
|
138
|
+
CalendarAssistant::Config::Keys::Settings.const_get k
|
139
|
+
end
|
140
|
+
setting_names.inject({}) do |settings, key|
|
141
|
+
settings[key] = setting key
|
142
|
+
settings
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
100
146
|
def tokens
|
101
147
|
Config.find_in_hash(user_config, Keys::TOKENS) ||
|
102
148
|
Config.set_in_hash(user_config, Keys::TOKENS, {})
|
@@ -118,6 +164,21 @@ class CalendarAssistant
|
|
118
164
|
end
|
119
165
|
end
|
120
166
|
|
167
|
+
#
|
168
|
+
# helper method for Keys::Options::ATTENDEES
|
169
|
+
#
|
170
|
+
def attendees
|
171
|
+
a = setting(Keys::Options::ATTENDEES)
|
172
|
+
if a.is_a?(String)
|
173
|
+
a = a.split(",")
|
174
|
+
end
|
175
|
+
a
|
176
|
+
end
|
177
|
+
|
178
|
+
def debug?
|
179
|
+
setting(Keys::Options::DEBUG)
|
180
|
+
end
|
181
|
+
|
121
182
|
private
|
122
183
|
|
123
184
|
def self.find_in_hash hash, keypath
|
@@ -150,5 +211,3 @@ class CalendarAssistant
|
|
150
211
|
end
|
151
212
|
end
|
152
213
|
end
|
153
|
-
|
154
|
-
require "calendar_assistant/config/token_store"
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class CalendarAssistant
|
2
|
+
module DateHelpers
|
3
|
+
def self.cast_dates attributes
|
4
|
+
attributes.each_with_object({}) do |(key, value), object|
|
5
|
+
if value.is_a?(Time) || value.is_a?(DateTime)
|
6
|
+
object[key] = Google::Apis::CalendarV3::EventDateTime.new(date_time: value)
|
7
|
+
elsif value.is_a?(Date)
|
8
|
+
object[key] = Google::Apis::CalendarV3::EventDateTime.new(date: value.to_s)
|
9
|
+
else
|
10
|
+
object[key] = value
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
@@ -1,10 +1,42 @@
|
|
1
1
|
class CalendarAssistant
|
2
2
|
class Event < SimpleDelegator
|
3
|
+
#
|
4
|
+
# constants describing enumerated attribute values
|
5
|
+
# see https://developers.google.com/calendar/v3/reference/events
|
6
|
+
#
|
7
|
+
module RealResponse
|
8
|
+
DECLINED = "declined"
|
9
|
+
ACCEPTED = "accepted"
|
10
|
+
NEEDS_ACTION = "needsAction"
|
11
|
+
TENTATIVE = "tentative"
|
12
|
+
end
|
13
|
+
|
14
|
+
module Response
|
15
|
+
include RealResponse
|
16
|
+
SELF = "self" # not part of Google's API, but useful to represent meetings-for-myself
|
17
|
+
end
|
3
18
|
|
19
|
+
module Transparency
|
20
|
+
TRANSPARENT = "transparent"
|
21
|
+
OPAQUE = "opaque"
|
22
|
+
end
|
23
|
+
|
24
|
+
module Visibility
|
25
|
+
DEFAULT = "default"
|
26
|
+
PUBLIC = "public"
|
27
|
+
PRIVATE = "private"
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# constants describing behavior
|
32
|
+
#
|
4
33
|
LOCATION_EVENT_REGEX = /^#{CalendarAssistant::EMOJI_WORLDMAP}/
|
5
34
|
|
35
|
+
#
|
36
|
+
# methods
|
37
|
+
#
|
6
38
|
def update **args
|
7
|
-
|
39
|
+
update!(**args)
|
8
40
|
self
|
9
41
|
end
|
10
42
|
|
@@ -13,14 +45,14 @@ class CalendarAssistant
|
|
13
45
|
end
|
14
46
|
|
15
47
|
def all_day?
|
16
|
-
|
48
|
+
start.try(:date) || self.end.try(:date)
|
17
49
|
end
|
18
50
|
|
19
51
|
def past?
|
20
52
|
if all_day?
|
21
|
-
Date.today >=
|
53
|
+
Date.today >= end_date
|
22
54
|
else
|
23
|
-
Time.now >=
|
55
|
+
Time.now >= end_time
|
24
56
|
end
|
25
57
|
end
|
26
58
|
|
@@ -30,18 +62,26 @@ class CalendarAssistant
|
|
30
62
|
|
31
63
|
def future?
|
32
64
|
if all_day?
|
33
|
-
|
65
|
+
start_date > Date.today
|
34
66
|
else
|
35
|
-
|
67
|
+
start_time > Time.now
|
36
68
|
end
|
37
69
|
end
|
38
70
|
|
39
71
|
def accepted?
|
40
|
-
response_status ==
|
72
|
+
response_status == CalendarAssistant::Event::Response::ACCEPTED
|
41
73
|
end
|
42
74
|
|
43
75
|
def declined?
|
44
|
-
response_status ==
|
76
|
+
response_status == CalendarAssistant::Event::Response::DECLINED
|
77
|
+
end
|
78
|
+
|
79
|
+
def awaiting?
|
80
|
+
response_status == CalendarAssistant::Event::Response::NEEDS_ACTION
|
81
|
+
end
|
82
|
+
|
83
|
+
def self?
|
84
|
+
response_status == CalendarAssistant::Event::Response::SELF
|
45
85
|
end
|
46
86
|
|
47
87
|
def one_on_one?
|
@@ -52,7 +92,7 @@ class CalendarAssistant
|
|
52
92
|
end
|
53
93
|
|
54
94
|
def busy?
|
55
|
-
transparency !=
|
95
|
+
transparency != CalendarAssistant::Event::Transparency::TRANSPARENT
|
56
96
|
end
|
57
97
|
|
58
98
|
def commitment?
|
@@ -62,20 +102,24 @@ class CalendarAssistant
|
|
62
102
|
end
|
63
103
|
|
64
104
|
def private?
|
65
|
-
visibility ==
|
105
|
+
visibility == CalendarAssistant::Event::Visibility::PRIVATE
|
66
106
|
end
|
67
107
|
|
68
108
|
def public?
|
69
|
-
visibility ==
|
109
|
+
visibility == CalendarAssistant::Event::Visibility::PUBLIC
|
70
110
|
end
|
71
111
|
|
72
112
|
def explicit_visibility?
|
73
113
|
private? || public?
|
74
114
|
end
|
75
115
|
|
116
|
+
def recurring?
|
117
|
+
!!recurring_event_id
|
118
|
+
end
|
119
|
+
|
76
120
|
def start_time
|
77
121
|
if all_day?
|
78
|
-
|
122
|
+
start_date.beginning_of_day
|
79
123
|
else
|
80
124
|
start.date_time
|
81
125
|
end
|
@@ -85,7 +129,23 @@ class CalendarAssistant
|
|
85
129
|
if all_day?
|
86
130
|
start.to_date
|
87
131
|
else
|
88
|
-
|
132
|
+
start_time.to_date
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def end_time
|
137
|
+
if all_day?
|
138
|
+
end_date.beginning_of_day
|
139
|
+
else
|
140
|
+
self.end.date_time
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def end_date
|
145
|
+
if all_day?
|
146
|
+
self.end.to_date
|
147
|
+
else
|
148
|
+
end_time.to_date
|
89
149
|
end
|
90
150
|
end
|
91
151
|
|
@@ -94,5 +154,55 @@ class CalendarAssistant
|
|
94
154
|
return "(no title)" if summary.nil? || summary.blank?
|
95
155
|
summary
|
96
156
|
end
|
157
|
+
|
158
|
+
def duration
|
159
|
+
if all_day?
|
160
|
+
days = (end_date - start_date).to_i
|
161
|
+
return "#{days}d"
|
162
|
+
end
|
163
|
+
|
164
|
+
p = ActiveSupport::Duration.build(end_time - start_time).parts
|
165
|
+
s = []
|
166
|
+
s << "#{p[:hours]}h" if p.has_key?(:hours)
|
167
|
+
s << "#{p[:minutes]}m" if p.has_key?(:minutes)
|
168
|
+
s.join(" ")
|
169
|
+
end
|
170
|
+
|
171
|
+
def human_attendees
|
172
|
+
return nil if attendees.nil?
|
173
|
+
attendees.select { |a| ! a.resource }
|
174
|
+
end
|
175
|
+
|
176
|
+
def attendee id
|
177
|
+
return nil if attendees.nil?
|
178
|
+
attendees.find do |attendee|
|
179
|
+
attendee.email == id
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def response_status
|
184
|
+
return CalendarAssistant::Event::Response::SELF if attendees.nil?
|
185
|
+
attendees.each do |attendee|
|
186
|
+
return attendee.response_status if attendee.self
|
187
|
+
end
|
188
|
+
nil
|
189
|
+
end
|
190
|
+
|
191
|
+
def av_uri
|
192
|
+
@av_uri ||= begin
|
193
|
+
description_link = CalendarAssistant::StringHelpers.find_uri_for_domain(description, "zoom.us")
|
194
|
+
return description_link if description_link
|
195
|
+
|
196
|
+
location_link = CalendarAssistant::StringHelpers.find_uri_for_domain(location, "zoom.us")
|
197
|
+
return location_link if location_link
|
198
|
+
|
199
|
+
return hangout_link if hangout_link
|
200
|
+
nil
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def contains? time
|
205
|
+
start_time <= time && time < end_time
|
206
|
+
end
|
97
207
|
end
|
98
|
-
end
|
208
|
+
end
|