cal-invite 0.1.1 → 0.1.2
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/CHANGELOG.md +14 -3
- data/README.md +20 -11
- data/lib/cal_invite/event.rb +77 -4
- data/lib/cal_invite/providers/base_provider.rb +24 -0
- data/lib/cal_invite/providers/google.rb +39 -36
- data/lib/cal_invite/providers/ical.rb +38 -29
- data/lib/cal_invite/providers/office365.rb +81 -0
- data/lib/cal_invite/providers/outlook.rb +47 -36
- data/lib/cal_invite/providers/yahoo.rb +34 -9
- data/lib/cal_invite/providers.rb +3 -1
- data/lib/cal_invite/version.rb +1 -1
- data/lib/cal_invite.rb +4 -0
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c410ac7a9f4a0163b9984196b0b9eb0f68974c92c9baf55fdd0d5b3048478dab
|
4
|
+
data.tar.gz: a9e3f6f5ed24978f5f891f2a8818f68fa6457f0d28570ff0d5a19c0b4460e3c1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7ae7e0b223af18c95eff52db6614ff44c4efc3277b146ab909a60582b86c66140e82bade3fc6507a9eb1f198c404de0896ffcd3bda8b7846bf7917d45500f7b5
|
7
|
+
data.tar.gz: 97e1766052ab48c325eaab422d06420e7f98d94cdddd6d9320f9605b5180a1fed783b9d5555bfff47f0ea1ceeaa15225d504f2b538def27395272e8a2752c140
|
data/CHANGELOG.md
CHANGED
@@ -1,10 +1,21 @@
|
|
1
|
-
|
1
|
+
# Cal Invite
|
2
|
+
|
3
|
+
## [Released]
|
4
|
+
|
5
|
+
## [v0.1.2] - 2024-12-17
|
2
6
|
|
3
|
-
|
7
|
+
- Add support to Microsoft office 365 calendar invite URL
|
8
|
+
- Update the README
|
9
|
+
- Add an example
|
10
|
+
- Better testing
|
11
|
+
|
12
|
+
## [v0.1.1] - 2024-12-17
|
4
13
|
|
5
14
|
Fixing a bug in the gemspec file
|
6
15
|
|
7
|
-
## [
|
16
|
+
## [Unreleased]
|
17
|
+
|
18
|
+
## [v0.1.0] - 2024-12-17
|
8
19
|
|
9
20
|
First public release of the Calendar Invite gem
|
10
21
|
|
data/README.md
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
-
# CalInvite
|
1
|
+
# 📅 CalInvite
|
2
2
|
|
3
3
|
A Ruby gem for generating calendar invitations across multiple calendar platforms with caching and webhook support.
|
4
4
|
|
5
5
|
[](https://badge.fury.io/rb/cal-invite)
|
6
|
-
|
6
|
+

|
7
|
+
|
8
|
+
[]
|
7
9
|
|
8
10
|
## Compatibility
|
9
11
|
|
@@ -15,18 +17,18 @@ A Ruby gem for generating calendar invitations across multiple calendar platform
|
|
15
17
|
Direct Integration:
|
16
18
|
- Apple iCal
|
17
19
|
- Microsoft Outlook
|
20
|
+
- Microsoft Outlook 365
|
18
21
|
- Google Calendar
|
19
22
|
- Yahoo Calendar
|
20
23
|
- Standard .ics file generation
|
21
24
|
|
22
25
|
Any calendar application that supports the iCalendar (.ics) standard should work, including but not limited to:
|
23
|
-
-
|
26
|
+
- Proton Calendar
|
24
27
|
- FastMail Calendar
|
25
28
|
- Thunderbird Calendar
|
26
29
|
- Zoho Calendar
|
27
30
|
- Microsoft Teams Calendar
|
28
31
|
- Zoom Calendar Integration
|
29
|
-
- Office 365 Calendar
|
30
32
|
|
31
33
|
## Installation
|
32
34
|
|
@@ -85,11 +87,12 @@ event = CalInvite::Event.new(
|
|
85
87
|
notes: "Bring your own laptop"
|
86
88
|
)
|
87
89
|
|
88
|
-
ical_url
|
89
|
-
google_url
|
90
|
-
outlook_url
|
91
|
-
|
92
|
-
|
90
|
+
ical_url = event.calendar_url(:ical)
|
91
|
+
google_url = event.calendar_url(:google)
|
92
|
+
outlook_url = event.calendar_url(:outlook)
|
93
|
+
outlook365_url = event.calendar_url(:office365)
|
94
|
+
yahoo_url = event.calendar_url(:yahoo)
|
95
|
+
ics_content = event.calendar_url(:ics)
|
93
96
|
```
|
94
97
|
|
95
98
|
## Development
|
@@ -98,9 +101,15 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
98
101
|
|
99
102
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
100
103
|
|
104
|
+
## Testing
|
105
|
+
|
106
|
+
Add test(s) as necessary.
|
107
|
+
|
108
|
+
Run all the tests before submiting: `bundle exec rake test`
|
109
|
+
|
101
110
|
## Contributing
|
102
111
|
|
103
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
112
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/the-pew-inc/cal-invite. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/the-pew-inc/cal-invite/blob/master/CODE_OF_CONDUCT.md).
|
104
113
|
|
105
114
|
## License
|
106
115
|
|
@@ -108,4 +117,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
108
117
|
|
109
118
|
## Code of Conduct
|
110
119
|
|
111
|
-
Everyone interacting in the Cal::Invite project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/
|
120
|
+
Everyone interacting in the Cal::Invite project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/the-pew-inc/cal-invite/blob/master/CODE_OF_CONDUCT.md).
|
data/lib/cal_invite/event.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
# lib/cal_invite/event.rb
|
4
4
|
module CalInvite
|
5
|
+
# Represents a calendar event with its properties and validation rules
|
5
6
|
class Event
|
6
7
|
attr_accessor :title,
|
7
8
|
:start_time,
|
@@ -13,31 +14,73 @@ module CalInvite
|
|
13
14
|
:timezone,
|
14
15
|
:show_attendees,
|
15
16
|
:notes,
|
16
|
-
:multi_day_sessions
|
17
|
+
:multi_day_sessions,
|
18
|
+
:all_day
|
17
19
|
|
20
|
+
# Initialize a new Event instance
|
21
|
+
#
|
22
|
+
# @param attributes [Hash] The attributes to initialize the event with
|
23
|
+
# @option attributes [String] :title The event title
|
24
|
+
# @option attributes [Time] :start_time The event start time
|
25
|
+
# @option attributes [Time] :end_time The event end time
|
26
|
+
# @option attributes [String] :description The event description
|
27
|
+
# @option attributes [String] :location The event location
|
28
|
+
# @option attributes [String] :url The event URL
|
29
|
+
# @option attributes [Array<String>] :attendees List of event attendees
|
30
|
+
# @option attributes [String] :timezone The event timezone (default: 'UTC')
|
31
|
+
# @option attributes [Boolean] :show_attendees Whether to show attendees (default: false)
|
32
|
+
# @option attributes [String] :notes Additional notes for the event
|
33
|
+
# @option attributes [Array<Hash>] :multi_day_sessions List of sessions for multi-day events
|
34
|
+
# @option attributes [Boolean] :all_day Whether this is an all-day event (default: false)
|
35
|
+
# @raise [ArgumentError] if required attributes are missing
|
18
36
|
def initialize(attributes = {})
|
19
37
|
@show_attendees = attributes.delete(:show_attendees) || false
|
20
38
|
@timezone = attributes.delete(:timezone) || 'UTC'
|
21
39
|
@multi_day_sessions = attributes.delete(:multi_day_sessions) || []
|
40
|
+
@all_day = attributes.delete(:all_day) || false
|
41
|
+
|
42
|
+
# Convert times to UTC before storing
|
43
|
+
if attributes[:start_time]
|
44
|
+
attributes[:start_time] = ensure_utc(attributes[:start_time])
|
45
|
+
end
|
46
|
+
if attributes[:end_time]
|
47
|
+
attributes[:end_time] = ensure_utc(attributes[:end_time])
|
48
|
+
end
|
22
49
|
|
23
50
|
attributes.each do |key, value|
|
24
51
|
send("#{key}=", value) if respond_to?("#{key}=")
|
25
52
|
end
|
53
|
+
|
54
|
+
validate!
|
26
55
|
end
|
27
56
|
|
57
|
+
# Generate a calendar URL for the specified provider
|
58
|
+
#
|
59
|
+
# @param provider [Symbol] The calendar provider to generate the URL for
|
60
|
+
# @return [String] The generated calendar URL
|
61
|
+
# @raise [ArgumentError] if required attributes are missing
|
28
62
|
def calendar_url(provider)
|
29
|
-
|
63
|
+
validate!
|
64
|
+
provider_class = CalInvite::Providers.const_get(capitalize_provider(provider.to_s))
|
30
65
|
generator = provider_class.new(self)
|
31
66
|
generator.generate
|
32
67
|
end
|
33
68
|
|
34
69
|
# Convert a UTC time to event's timezone
|
70
|
+
#
|
71
|
+
# @param time [Time, nil] The time to convert
|
72
|
+
# @return [Time, nil] The converted time in the event's timezone
|
35
73
|
def localize_time(time)
|
36
74
|
return time unless time
|
37
|
-
time
|
75
|
+
# When timezone is UTC, we should preserve the UTC time
|
76
|
+
# without any conversion
|
77
|
+
return time if timezone == 'UTC'
|
78
|
+
time.getlocal(timezone_offset)
|
38
79
|
end
|
39
80
|
|
40
|
-
# Get all event sessions
|
81
|
+
# Get all event sessions including multi-day sessions
|
82
|
+
#
|
83
|
+
# @return [Array<Array<Time>>] Array of start and end time pairs
|
41
84
|
def sessions
|
42
85
|
return [@start_time, @end_time] if multi_day_sessions.empty?
|
43
86
|
|
@@ -45,5 +88,35 @@ module CalInvite
|
|
45
88
|
[session[:start_time], session[:end_time]]
|
46
89
|
end
|
47
90
|
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def ensure_utc(time)
|
95
|
+
return nil unless time
|
96
|
+
time.is_a?(Time) ? time.utc : Time.parse(time.to_s).utc
|
97
|
+
end
|
98
|
+
|
99
|
+
def timezone_offset
|
100
|
+
return '+00:00' if timezone == 'UTC'
|
101
|
+
timezone # assume timezone is already in offset format
|
102
|
+
end
|
103
|
+
|
104
|
+
def capitalize_provider(string)
|
105
|
+
# Handles both simple capitalization (ical -> Ical)
|
106
|
+
# and compound names (office365 -> Office365)
|
107
|
+
string.split('_').map(&:capitalize).join
|
108
|
+
end
|
109
|
+
|
110
|
+
# Validate the event attributes
|
111
|
+
#
|
112
|
+
# @raise [ArgumentError] if required attributes are missing
|
113
|
+
def validate!
|
114
|
+
raise ArgumentError, "Title is required" if title.nil? || title.strip.empty?
|
115
|
+
|
116
|
+
unless all_day
|
117
|
+
raise ArgumentError, "Start time is required for non-all-day events" if start_time.nil?
|
118
|
+
raise ArgumentError, "End time is required for non-all-day events" if end_time.nil?
|
119
|
+
end
|
120
|
+
end
|
48
121
|
end
|
49
122
|
end
|
@@ -3,23 +3,41 @@
|
|
3
3
|
# lib/cal_invite/providers/base_provider.rb
|
4
4
|
module CalInvite
|
5
5
|
module Providers
|
6
|
+
# Base class for calendar providers that implements common functionality
|
7
|
+
# and defines the interface that all providers must implement
|
6
8
|
class BaseProvider
|
9
|
+
# @return [CalInvite::Event] The event being processed
|
7
10
|
attr_reader :event
|
8
11
|
|
12
|
+
# Initialize a new calendar provider
|
13
|
+
#
|
14
|
+
# @param event [CalInvite::Event] The event to generate a calendar URL for
|
9
15
|
def initialize(event)
|
10
16
|
@event = event
|
11
17
|
end
|
12
18
|
|
19
|
+
# Generate a calendar URL for the event
|
20
|
+
# This method must be implemented by all provider subclasses
|
21
|
+
#
|
22
|
+
# @abstract
|
23
|
+
# @raise [NotImplementedError] if the provider class doesn't implement this method
|
13
24
|
def generate
|
14
25
|
raise NotImplementedError, "#{self.class} must implement #generate"
|
15
26
|
end
|
16
27
|
|
17
28
|
protected
|
18
29
|
|
30
|
+
# URL encode a string for use in calendar URLs
|
31
|
+
#
|
32
|
+
# @param str [#to_s] The string to encode
|
33
|
+
# @return [String] The URL encoded string
|
19
34
|
def url_encode(str)
|
20
35
|
URI.encode_www_form_component(str.to_s)
|
21
36
|
end
|
22
37
|
|
38
|
+
# Format the event description including notes and URL if present
|
39
|
+
#
|
40
|
+
# @return [String, nil] The formatted description or nil if no content
|
23
41
|
def format_description
|
24
42
|
parts = []
|
25
43
|
parts << event.description if event.description
|
@@ -28,6 +46,9 @@ module CalInvite
|
|
28
46
|
parts.join("\n\n")
|
29
47
|
end
|
30
48
|
|
49
|
+
# Format the event location, combining physical location and URL if both present
|
50
|
+
#
|
51
|
+
# @return [String, nil] The formatted location or nil if neither location nor URL present
|
31
52
|
def format_location
|
32
53
|
return event.url if event.url && !event.location
|
33
54
|
return event.location if event.location && !event.url
|
@@ -35,6 +56,9 @@ module CalInvite
|
|
35
56
|
nil
|
36
57
|
end
|
37
58
|
|
59
|
+
# Get the list of attendees if showing attendees is enabled
|
60
|
+
#
|
61
|
+
# @return [Array<String>] The list of attendees or empty array if disabled/none present
|
38
62
|
def attendees_list
|
39
63
|
return [] unless event.show_attendees && event.attendees&.any?
|
40
64
|
event.attendees
|
@@ -4,11 +4,9 @@
|
|
4
4
|
module CalInvite
|
5
5
|
module Providers
|
6
6
|
class Google < BaseProvider
|
7
|
-
BASE_URL = "https://calendar.google.com/calendar/render"
|
8
|
-
|
9
7
|
def generate
|
10
|
-
if event.
|
11
|
-
|
8
|
+
if event.all_day
|
9
|
+
generate_all_day_event
|
12
10
|
else
|
13
11
|
generate_single_event
|
14
12
|
end
|
@@ -16,50 +14,55 @@ module CalInvite
|
|
16
14
|
|
17
15
|
private
|
18
16
|
|
19
|
-
def
|
17
|
+
def generate_all_day_event
|
20
18
|
params = {
|
21
|
-
action:
|
22
|
-
text: event.title,
|
23
|
-
dates:
|
24
|
-
details: format_description,
|
25
|
-
location: format_location,
|
26
|
-
ctz: event.timezone
|
19
|
+
action: 'TEMPLATE',
|
20
|
+
text: url_encode(event.title),
|
21
|
+
dates: format_all_day_dates
|
27
22
|
}
|
28
23
|
|
29
|
-
|
30
|
-
|
31
|
-
end
|
32
|
-
|
33
|
-
"#{BASE_URL}?#{URI.encode_www_form(params)}"
|
24
|
+
add_optional_params(params)
|
25
|
+
build_url(params)
|
34
26
|
end
|
35
27
|
|
36
|
-
def
|
37
|
-
# For multi-day events, Google Calendar supports recurring events
|
38
|
-
sessions = event.multi_day_sessions.map do |session|
|
39
|
-
format_dates(session[:start_time], session[:end_time])
|
40
|
-
end
|
41
|
-
|
28
|
+
def generate_single_event
|
42
29
|
params = {
|
43
|
-
action:
|
44
|
-
text: event.title,
|
45
|
-
dates:
|
46
|
-
details: format_description,
|
47
|
-
location: format_location,
|
48
|
-
ctz: event.timezone
|
30
|
+
action: 'TEMPLATE',
|
31
|
+
text: url_encode(event.title),
|
32
|
+
dates: format_dates
|
49
33
|
}
|
50
34
|
|
51
|
-
|
52
|
-
|
53
|
-
|
35
|
+
add_optional_params(params)
|
36
|
+
build_url(params)
|
37
|
+
end
|
38
|
+
|
39
|
+
def format_all_day_dates
|
40
|
+
# For all-day events, use current date if no start_time specified
|
41
|
+
start_date = event.start_time || Time.now
|
42
|
+
end_date = event.end_time || (start_date + 86400) # Add one day if no end_time
|
54
43
|
|
55
|
-
"#{
|
44
|
+
"#{start_date.strftime('%Y%m%d')}/#{end_date.strftime('%Y%m%d')}"
|
56
45
|
end
|
57
46
|
|
58
|
-
def format_dates
|
59
|
-
|
60
|
-
|
47
|
+
def format_dates
|
48
|
+
raise ArgumentError, "Start time is required" unless event.start_time
|
49
|
+
raise ArgumentError, "End time is required" unless event.end_time
|
50
|
+
|
51
|
+
start_time = event.start_time
|
52
|
+
end_time = event.end_time
|
53
|
+
|
54
|
+
"#{start_time.utc.strftime('%Y%m%dT%H%M%SZ')}/#{end_time.utc.strftime('%Y%m%dT%H%M%SZ')}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def add_optional_params(params)
|
58
|
+
params[:details] = url_encode(format_description) if format_description
|
59
|
+
params[:location] = url_encode(format_location) if format_location
|
60
|
+
params
|
61
|
+
end
|
61
62
|
|
62
|
-
|
63
|
+
def build_url(params)
|
64
|
+
query = params.map { |k, v| "#{k}=#{v}" }.join('&')
|
65
|
+
"https://calendar.google.com/calendar/render?#{query}"
|
63
66
|
end
|
64
67
|
end
|
65
68
|
end
|
@@ -8,61 +8,70 @@ module CalInvite
|
|
8
8
|
[
|
9
9
|
"BEGIN:VCALENDAR",
|
10
10
|
"VERSION:2.0",
|
11
|
-
"PRODID:-//CalInvite//EN",
|
11
|
+
"PRODID:-//CalInvite//Ruby//EN",
|
12
12
|
generate_events,
|
13
13
|
"END:VCALENDAR"
|
14
|
-
].
|
14
|
+
].join("\n")
|
15
15
|
end
|
16
16
|
|
17
17
|
private
|
18
18
|
|
19
19
|
def generate_events
|
20
20
|
if event.multi_day_sessions.any?
|
21
|
-
event.multi_day_sessions.map { |session| generate_vevent(session
|
21
|
+
event.multi_day_sessions.map { |session| generate_vevent(session) }.join("\n")
|
22
22
|
else
|
23
|
-
|
23
|
+
generate_vevent
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
def generate_vevent(
|
28
|
-
|
29
|
-
"BEGIN:VEVENT",
|
30
|
-
"UID:#{generate_uid}",
|
31
|
-
"DTSTAMP:#{format_time(Time.now)}",
|
32
|
-
"DTSTART;TZID=#{event.timezone}:#{format_time(start_time)}",
|
33
|
-
"DTEND;TZID=#{event.timezone}:#{format_time(end_time)}",
|
34
|
-
"SUMMARY:#{event.title}",
|
35
|
-
"DESCRIPTION:#{format_description}",
|
36
|
-
]
|
27
|
+
def generate_vevent(session = nil)
|
28
|
+
lines = ["BEGIN:VEVENT"]
|
37
29
|
|
38
|
-
if
|
39
|
-
|
40
|
-
|
30
|
+
if event.all_day
|
31
|
+
start_date = event.start_time || Time.now
|
32
|
+
end_date = event.end_time || (start_date + 86400)
|
33
|
+
|
34
|
+
lines << "DTSTART;VALUE=DATE:#{format_date(start_date)}"
|
35
|
+
lines << "DTEND;VALUE=DATE:#{format_date(end_date)}"
|
36
|
+
else
|
37
|
+
start_time = session ? session[:start_time] : event.start_time
|
38
|
+
end_time = session ? session[:end_time] : event.end_time
|
39
|
+
|
40
|
+
raise ArgumentError, "Start time is required for non-all-day events" unless start_time
|
41
|
+
raise ArgumentError, "End time is required for non-all-day events" unless end_time
|
41
42
|
|
42
|
-
|
43
|
-
|
43
|
+
lines << "DTSTART:#{format_time(start_time)}"
|
44
|
+
lines << "DTEND:#{format_time(end_time)}"
|
44
45
|
end
|
45
46
|
|
46
|
-
|
47
|
-
|
48
|
-
|
47
|
+
lines.concat([
|
48
|
+
"SUMMARY:#{event.title}",
|
49
|
+
"UID:#{generate_uid}"
|
50
|
+
])
|
51
|
+
|
52
|
+
lines << "DESCRIPTION:#{format_description}" if format_description
|
53
|
+
lines << "LOCATION:#{format_location}" if format_location
|
54
|
+
|
55
|
+
if attendees = attendees_list
|
56
|
+
attendees.each do |attendee|
|
57
|
+
lines << "ATTENDEE:mailto:#{attendee}"
|
49
58
|
end
|
50
59
|
end
|
51
60
|
|
52
|
-
|
53
|
-
|
61
|
+
lines << "END:VEVENT"
|
62
|
+
lines.join("\n")
|
54
63
|
end
|
55
64
|
|
56
65
|
def generate_uid
|
57
|
-
|
66
|
+
SecureRandom.uuid
|
58
67
|
end
|
59
68
|
|
60
|
-
def
|
61
|
-
|
69
|
+
def format_date(time)
|
70
|
+
time.strftime("%Y%m%d")
|
62
71
|
end
|
63
72
|
|
64
|
-
def
|
65
|
-
|
73
|
+
def format_time(time)
|
74
|
+
time.utc.strftime("%Y%m%dT%H%M%SZ")
|
66
75
|
end
|
67
76
|
end
|
68
77
|
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# lib/cal_invite/providers/office365.rb
|
4
|
+
module CalInvite
|
5
|
+
module Providers
|
6
|
+
class Office365 < BaseProvider
|
7
|
+
def generate
|
8
|
+
if event.all_day
|
9
|
+
generate_all_day_event
|
10
|
+
else
|
11
|
+
generate_single_event
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def generate_all_day_event
|
18
|
+
params = {
|
19
|
+
subject: url_encode(event.title),
|
20
|
+
path: '/calendar/action/compose',
|
21
|
+
allday: 'true'
|
22
|
+
}
|
23
|
+
|
24
|
+
# Use current date if no start_time specified
|
25
|
+
start_date = event.start_time || Time.now
|
26
|
+
end_date = event.end_time || (start_date + 86400) # Add one day if no end_time
|
27
|
+
|
28
|
+
params[:startdt] = url_encode(format_date(start_date))
|
29
|
+
params[:enddt] = url_encode(format_date(end_date))
|
30
|
+
|
31
|
+
add_optional_params(params)
|
32
|
+
build_url(params)
|
33
|
+
end
|
34
|
+
|
35
|
+
def generate_single_event
|
36
|
+
params = {
|
37
|
+
subject: url_encode(event.title),
|
38
|
+
path: '/calendar/action/compose'
|
39
|
+
}
|
40
|
+
|
41
|
+
raise ArgumentError, "Start time is required" unless event.start_time
|
42
|
+
raise ArgumentError, "End time is required" unless event.end_time
|
43
|
+
|
44
|
+
params[:startdt] = url_encode(format_time(event.start_time))
|
45
|
+
params[:enddt] = url_encode(format_time(event.end_time))
|
46
|
+
|
47
|
+
add_optional_params(params)
|
48
|
+
build_url(params)
|
49
|
+
end
|
50
|
+
|
51
|
+
def format_date(time)
|
52
|
+
time.strftime('%Y-%m-%d')
|
53
|
+
end
|
54
|
+
|
55
|
+
def format_time(time)
|
56
|
+
time.utc.strftime('%Y-%m-%dT%H:%M:%S')
|
57
|
+
end
|
58
|
+
|
59
|
+
def add_optional_params(params)
|
60
|
+
if description = format_description
|
61
|
+
params[:body] = url_encode(description)
|
62
|
+
end
|
63
|
+
|
64
|
+
if location = format_location
|
65
|
+
params[:location] = url_encode(location)
|
66
|
+
end
|
67
|
+
|
68
|
+
if attendees = attendees_list
|
69
|
+
params[:to] = url_encode(attendees.join(';'))
|
70
|
+
end
|
71
|
+
|
72
|
+
params
|
73
|
+
end
|
74
|
+
|
75
|
+
def build_url(params)
|
76
|
+
query = params.map { |k, v| "#{k}=#{v}" }.join('&')
|
77
|
+
"https://outlook.office.com/owa/?#{query}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -4,11 +4,9 @@
|
|
4
4
|
module CalInvite
|
5
5
|
module Providers
|
6
6
|
class Outlook < BaseProvider
|
7
|
-
BASE_URL = "https://outlook.live.com/calendar/0/deeplink/compose"
|
8
|
-
|
9
7
|
def generate
|
10
|
-
if event.
|
11
|
-
|
8
|
+
if event.all_day
|
9
|
+
generate_all_day_event
|
12
10
|
else
|
13
11
|
generate_single_event
|
14
12
|
end
|
@@ -16,49 +14,62 @@ module CalInvite
|
|
16
14
|
|
17
15
|
private
|
18
16
|
|
17
|
+
def generate_all_day_event
|
18
|
+
params = {
|
19
|
+
path: '/calendar/0/action/compose',
|
20
|
+
subject: url_encode(event.title),
|
21
|
+
allday: 'true'
|
22
|
+
}
|
23
|
+
|
24
|
+
# Use current date if no start_time specified
|
25
|
+
start_date = event.start_time || Time.now
|
26
|
+
end_date = event.end_time || (start_date + 86400) # Add one day if no end_time
|
27
|
+
|
28
|
+
params[:startdt] = format_date(start_date)
|
29
|
+
params[:enddt] = format_date(end_date)
|
30
|
+
|
31
|
+
add_optional_params(params)
|
32
|
+
build_url(params)
|
33
|
+
end
|
34
|
+
|
19
35
|
def generate_single_event
|
20
36
|
params = {
|
21
|
-
|
22
|
-
|
23
|
-
enddt: format_time(event.end_time),
|
24
|
-
body: format_description,
|
25
|
-
location: format_location,
|
26
|
-
path: '/calendar/action/compose',
|
27
|
-
rru: 'addevent'
|
37
|
+
path: '/calendar/0/action/compose',
|
38
|
+
subject: url_encode(event.title)
|
28
39
|
}
|
29
40
|
|
30
|
-
|
31
|
-
|
32
|
-
end
|
41
|
+
raise ArgumentError, "Start time is required" unless event.start_time
|
42
|
+
raise ArgumentError, "End time is required" unless event.end_time
|
33
43
|
|
34
|
-
|
44
|
+
params[:startdt] = format_time(event.start_time)
|
45
|
+
params[:enddt] = format_time(event.end_time)
|
46
|
+
|
47
|
+
add_optional_params(params)
|
48
|
+
build_url(params)
|
49
|
+
end
|
50
|
+
|
51
|
+
def format_date(time)
|
52
|
+
time.strftime('%Y-%m-%d')
|
53
|
+
end
|
54
|
+
|
55
|
+
def format_time(time)
|
56
|
+
time.utc.strftime('%Y-%m-%dT%H:%M:%S')
|
35
57
|
end
|
36
58
|
|
37
|
-
def
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
enddt: format_time(session[:end_time]),
|
44
|
-
body: format_description,
|
45
|
-
location: format_location,
|
46
|
-
path: '/calendar/action/compose',
|
47
|
-
rru: 'addevent'
|
48
|
-
}
|
49
|
-
|
50
|
-
if attendees_list.any?
|
51
|
-
params[:to] = attendees_list.join(';')
|
52
|
-
end
|
53
|
-
|
54
|
-
"#{BASE_URL}?#{URI.encode_www_form(params)}"
|
59
|
+
def add_optional_params(params)
|
60
|
+
params[:body] = url_encode(format_description) if format_description
|
61
|
+
params[:location] = url_encode(format_location) if format_location
|
62
|
+
|
63
|
+
if attendees = attendees_list
|
64
|
+
params[:to] = url_encode(attendees.join(';'))
|
55
65
|
end
|
56
66
|
|
57
|
-
|
67
|
+
params
|
58
68
|
end
|
59
69
|
|
60
|
-
def
|
61
|
-
|
70
|
+
def build_url(params)
|
71
|
+
query = params.map { |k, v| "#{k}=#{v}" }.join('&')
|
72
|
+
"https://outlook.live.com/calendar/0/action/compose?#{query}"
|
62
73
|
end
|
63
74
|
end
|
64
75
|
end
|
@@ -7,7 +7,9 @@ module CalInvite
|
|
7
7
|
BASE_URL = "https://calendar.yahoo.com"
|
8
8
|
|
9
9
|
def generate
|
10
|
-
if event.
|
10
|
+
if event.all_day
|
11
|
+
generate_all_day_event
|
12
|
+
elsif event.multi_day_sessions.any?
|
11
13
|
generate_multi_day_event
|
12
14
|
else
|
13
15
|
generate_single_event
|
@@ -16,14 +18,37 @@ module CalInvite
|
|
16
18
|
|
17
19
|
private
|
18
20
|
|
21
|
+
def generate_all_day_event
|
22
|
+
start_date = event.start_time || Time.now
|
23
|
+
end_date = event.end_time || (start_date + 86400)
|
24
|
+
|
25
|
+
params = {
|
26
|
+
v: 60,
|
27
|
+
view: 'd',
|
28
|
+
type: 20,
|
29
|
+
title: event.title,
|
30
|
+
st: format_date(start_date),
|
31
|
+
et: format_date(end_date),
|
32
|
+
desc: format_description,
|
33
|
+
in_loc: format_location,
|
34
|
+
crnd: event.timezone,
|
35
|
+
allday: 'true'
|
36
|
+
}
|
37
|
+
|
38
|
+
"#{BASE_URL}/?#{URI.encode_www_form(params)}"
|
39
|
+
end
|
40
|
+
|
19
41
|
def generate_single_event
|
42
|
+
raise ArgumentError, "Start time is required" unless event.start_time
|
43
|
+
raise ArgumentError, "End time is required" unless event.end_time
|
44
|
+
|
20
45
|
params = {
|
21
46
|
v: 60,
|
22
47
|
view: 'd',
|
23
48
|
type: 20,
|
24
49
|
title: event.title,
|
25
|
-
st:
|
26
|
-
et:
|
50
|
+
st: format_time(event.start_time),
|
51
|
+
et: format_time(event.end_time),
|
27
52
|
desc: format_description,
|
28
53
|
in_loc: format_location,
|
29
54
|
crnd: event.timezone
|
@@ -41,8 +66,8 @@ module CalInvite
|
|
41
66
|
view: 'd',
|
42
67
|
type: 20,
|
43
68
|
title: event.title,
|
44
|
-
st:
|
45
|
-
et:
|
69
|
+
st: format_time(session[:start_time]),
|
70
|
+
et: format_time(session[:end_time]),
|
46
71
|
desc: format_description,
|
47
72
|
in_loc: format_location,
|
48
73
|
crnd: event.timezone
|
@@ -54,12 +79,12 @@ module CalInvite
|
|
54
79
|
sessions.join("\n")
|
55
80
|
end
|
56
81
|
|
57
|
-
def
|
58
|
-
|
82
|
+
def format_date(time)
|
83
|
+
time.strftime("%Y%m%d")
|
59
84
|
end
|
60
85
|
|
61
|
-
def
|
62
|
-
|
86
|
+
def format_time(time)
|
87
|
+
time.utc.strftime("%Y%m%dT%H%M%S")
|
63
88
|
end
|
64
89
|
end
|
65
90
|
end
|
data/lib/cal_invite/providers.rb
CHANGED
@@ -3,13 +3,15 @@
|
|
3
3
|
# lib/cal_invite/providers.rb
|
4
4
|
module CalInvite
|
5
5
|
module Providers
|
6
|
-
SUPPORTED_PROVIDERS = %i[google ical outlook yahoo ics].freeze
|
6
|
+
SUPPORTED_PROVIDERS = %i[google ical outlook yahoo ics office365].freeze
|
7
7
|
|
8
8
|
autoload :BaseProvider, 'cal_invite/providers/base_provider'
|
9
9
|
autoload :Google, 'cal_invite/providers/google'
|
10
10
|
autoload :Ical, 'cal_invite/providers/ical'
|
11
11
|
autoload :Outlook, 'cal_invite/providers/outlook'
|
12
|
+
autoload :Office365, 'cal_invite/providers/office365'
|
12
13
|
autoload :Yahoo, 'cal_invite/providers/yahoo'
|
13
14
|
autoload :Ics, 'cal_invite/providers/ics'
|
15
|
+
|
14
16
|
end
|
15
17
|
end
|
data/lib/cal_invite/version.rb
CHANGED
data/lib/cal_invite.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cal-invite
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephane Paquet
|
@@ -10,9 +10,8 @@ bindir: exe
|
|
10
10
|
cert_chain: []
|
11
11
|
date: 2024-12-17 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description: CalInvite
|
14
|
-
|
15
|
-
calendards such as Google Calendar, Microsoft Outlook, Apple Calendar and many more.
|
13
|
+
description: CalInvite provides a simple way to generate calendar invite URLs for
|
14
|
+
various providers (Google, Outlook, Yahoo) and ICS files
|
16
15
|
email:
|
17
16
|
- 176050+spaquet@users.noreply.github.com
|
18
17
|
executables: []
|
@@ -36,6 +35,7 @@ files:
|
|
36
35
|
- lib/cal_invite/providers/google.rb
|
37
36
|
- lib/cal_invite/providers/ical.rb
|
38
37
|
- lib/cal_invite/providers/ics.rb
|
38
|
+
- lib/cal_invite/providers/office365.rb
|
39
39
|
- lib/cal_invite/providers/outlook.rb
|
40
40
|
- lib/cal_invite/providers/yahoo.rb
|
41
41
|
- lib/cal_invite/version.rb
|
@@ -65,5 +65,5 @@ requirements: []
|
|
65
65
|
rubygems_version: 3.5.22
|
66
66
|
signing_key:
|
67
67
|
specification_version: 4
|
68
|
-
summary:
|
68
|
+
summary: A Ruby gem for generating calendar invite URLs and ICS files
|
69
69
|
test_files: []
|