cal-invite 0.1.1 → 0.1.3
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/CACHING.md +148 -0
- data/CHANGELOG.md +22 -3
- data/README.md +159 -19
- data/lib/cal_invite/caching.rb +74 -0
- data/lib/cal_invite/configuration.rb +39 -3
- data/lib/cal_invite/event.rb +96 -14
- data/lib/cal_invite/providers/base_provider.rb +63 -41
- data/lib/cal_invite/providers/google.rb +43 -36
- data/lib/cal_invite/providers/ical.rb +70 -28
- data/lib/cal_invite/providers/ics.rb +41 -18
- data/lib/cal_invite/providers/ics_content.rb +111 -0
- data/lib/cal_invite/providers/office365.rb +81 -0
- data/lib/cal_invite/providers/outlook.rb +51 -37
- data/lib/cal_invite/providers/yahoo.rb +40 -12
- data/lib/cal_invite/providers.rb +6 -2
- data/lib/cal_invite/version.rb +1 -1
- data/lib/cal_invite.rb +10 -0
- metadata +24 -7
@@ -0,0 +1,111 @@
|
|
1
|
+
# lib/cal_invite/providers/ics_content.rb
|
2
|
+
module CalInvite
|
3
|
+
module Providers
|
4
|
+
class IcsContent < BaseProvider
|
5
|
+
def generate
|
6
|
+
calendar_lines = [
|
7
|
+
"BEGIN:VCALENDAR",
|
8
|
+
"VERSION:2.0",
|
9
|
+
"PRODID:-//CalInvite//EN",
|
10
|
+
"CALSCALE:GREGORIAN",
|
11
|
+
"METHOD:PUBLISH"
|
12
|
+
]
|
13
|
+
|
14
|
+
if event.multi_day_sessions.any?
|
15
|
+
event.multi_day_sessions.each do |session|
|
16
|
+
calendar_lines.concat(generate_vevent(session[:start_time], session[:end_time]))
|
17
|
+
end
|
18
|
+
else
|
19
|
+
calendar_lines.concat(generate_vevent(event.start_time, event.end_time))
|
20
|
+
end
|
21
|
+
|
22
|
+
calendar_lines << "END:VCALENDAR"
|
23
|
+
calendar_lines.join("\r\n")
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def generate_vevent(start_time, end_time)
|
29
|
+
[
|
30
|
+
"BEGIN:VEVENT",
|
31
|
+
"UID:#{generate_uid}",
|
32
|
+
"DTSTAMP:#{format_timestamp(Time.now.utc)}",
|
33
|
+
"DTSTART:#{format_timestamp(start_time)}",
|
34
|
+
"DTEND:#{format_timestamp(end_time)}",
|
35
|
+
"SUMMARY:#{escape_text(event.title)}",
|
36
|
+
description_line,
|
37
|
+
location_line,
|
38
|
+
url_line,
|
39
|
+
attendee_lines,
|
40
|
+
"END:VEVENT"
|
41
|
+
].compact
|
42
|
+
end
|
43
|
+
|
44
|
+
def format_timestamp(time)
|
45
|
+
time.utc.strftime("%Y%m%dT%H%M%SZ")
|
46
|
+
end
|
47
|
+
|
48
|
+
def generate_uid
|
49
|
+
"#{Time.now.to_i}-#{SecureRandom.hex(8)}@cal-invite"
|
50
|
+
end
|
51
|
+
|
52
|
+
def escape_text(text)
|
53
|
+
return '' if text.nil?
|
54
|
+
text.to_s
|
55
|
+
.gsub('\\', '\\\\')
|
56
|
+
.gsub("\n", '\\n')
|
57
|
+
.gsub(',', '\\,')
|
58
|
+
.gsub(';', '\\;')
|
59
|
+
end
|
60
|
+
|
61
|
+
def description_line
|
62
|
+
return nil unless event.description
|
63
|
+
"DESCRIPTION:#{escape_text(event.description)}"
|
64
|
+
end
|
65
|
+
|
66
|
+
def location_line
|
67
|
+
return nil unless event.location
|
68
|
+
"LOCATION:#{escape_text(event.location)}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def url_line
|
72
|
+
return nil unless event.url
|
73
|
+
"URL:#{escape_text(event.url)}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def attendee_lines
|
77
|
+
return nil unless event.show_attendees && event.attendees&.any?
|
78
|
+
event.attendees.map { |attendee| "ATTENDEE;RSVP=TRUE:mailto:#{attendee}" }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Optional download wrapper
|
83
|
+
module IcsDownload
|
84
|
+
def self.headers(filename)
|
85
|
+
{
|
86
|
+
'Content-Type' => 'text/calendar; charset=UTF-8',
|
87
|
+
'Content-Disposition' => "attachment; filename=#{sanitize_filename(filename)}"
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.sanitize_filename(filename)
|
92
|
+
filename.gsub(/[^0-9A-Za-z.\-]/, '_')
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.wrap_for_download(content, title)
|
96
|
+
filename = sanitize_filename("#{title.downcase}_#{Time.now.strftime('%Y%m%d')}.ics")
|
97
|
+
{
|
98
|
+
content: content,
|
99
|
+
headers: headers(filename)
|
100
|
+
}
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# For compatibility, keep the original classes but inherit from IcsContent
|
105
|
+
class Ics < IcsContent
|
106
|
+
end
|
107
|
+
|
108
|
+
class Ical < IcsContent
|
109
|
+
end
|
110
|
+
end
|
111
|
+
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
|
+
# Always use UTC format, timezone is handled by the calendar
|
57
|
+
time.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
58
|
+
end
|
59
|
+
|
60
|
+
def add_optional_params(params)
|
61
|
+
description_parts = []
|
62
|
+
description_parts << format_description if format_description
|
63
|
+
description_parts << "Virtual Meeting URL: #{format_url}" if format_url
|
64
|
+
params[:body] = url_encode(description_parts.join("\n\n")) if description_parts.any?
|
65
|
+
|
66
|
+
params[:location] = url_encode(format_location) if format_location
|
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
|
@@ -1,14 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# lib/cal_invite/providers/outlook.rb
|
4
3
|
module CalInvite
|
5
4
|
module Providers
|
6
5
|
class Outlook < BaseProvider
|
7
|
-
BASE_URL = "https://outlook.live.com/calendar/0/deeplink/compose"
|
8
|
-
|
9
6
|
def generate
|
10
|
-
if event.
|
11
|
-
|
7
|
+
if event.all_day
|
8
|
+
generate_all_day_event
|
12
9
|
else
|
13
10
|
generate_single_event
|
14
11
|
end
|
@@ -16,49 +13,66 @@ module CalInvite
|
|
16
13
|
|
17
14
|
private
|
18
15
|
|
16
|
+
def generate_all_day_event
|
17
|
+
params = {
|
18
|
+
path: '/calendar/0/action/compose',
|
19
|
+
subject: url_encode(event.title),
|
20
|
+
allday: 'true'
|
21
|
+
}
|
22
|
+
|
23
|
+
start_date = event.start_time || Time.now
|
24
|
+
end_date = event.end_time || (start_date + 86400)
|
25
|
+
|
26
|
+
params[:startdt] = format_date(start_date)
|
27
|
+
params[:enddt] = format_date(end_date)
|
28
|
+
|
29
|
+
add_optional_params(params)
|
30
|
+
build_url(params)
|
31
|
+
end
|
32
|
+
|
19
33
|
def generate_single_event
|
20
34
|
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'
|
35
|
+
path: '/calendar/0/action/compose',
|
36
|
+
subject: url_encode(event.title)
|
28
37
|
}
|
29
38
|
|
30
|
-
|
31
|
-
|
32
|
-
end
|
39
|
+
raise ArgumentError, "Start time is required" unless event.start_time
|
40
|
+
raise ArgumentError, "End time is required" unless event.end_time
|
33
41
|
|
34
|
-
|
42
|
+
params[:startdt] = format_time(event.start_time)
|
43
|
+
params[:enddt] = format_time(event.end_time)
|
44
|
+
|
45
|
+
add_optional_params(params)
|
46
|
+
build_url(params)
|
47
|
+
end
|
48
|
+
|
49
|
+
def format_date(time)
|
50
|
+
time.strftime('%Y-%m-%d')
|
35
51
|
end
|
36
52
|
|
37
|
-
def
|
38
|
-
#
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
end
|
53
|
-
|
54
|
-
"#{BASE_URL}?#{URI.encode_www_form(params)}"
|
53
|
+
def format_time(time)
|
54
|
+
# Always use UTC format, timezone is handled by the calendar
|
55
|
+
time.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
56
|
+
end
|
57
|
+
|
58
|
+
def add_optional_params(params)
|
59
|
+
description_parts = []
|
60
|
+
description_parts << format_description if format_description
|
61
|
+
description_parts << "Virtual Meeting URL: #{format_url}" if format_url
|
62
|
+
params[:body] = url_encode(description_parts.join("\n\n")) if description_parts.any?
|
63
|
+
|
64
|
+
params[:location] = url_encode(format_location) if format_location
|
65
|
+
|
66
|
+
if attendees = attendees_list
|
67
|
+
params[:to] = url_encode(attendees.join(';'))
|
55
68
|
end
|
56
69
|
|
57
|
-
|
70
|
+
params
|
58
71
|
end
|
59
72
|
|
60
|
-
def
|
61
|
-
|
73
|
+
def build_url(params)
|
74
|
+
query = params.map { |k, v| "#{k}=#{v}" }.join('&')
|
75
|
+
"https://outlook.live.com/calendar/0/action/compose?#{query}"
|
62
76
|
end
|
63
77
|
end
|
64
78
|
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,16 +18,43 @@ module CalInvite
|
|
16
18
|
|
17
19
|
private
|
18
20
|
|
19
|
-
def
|
21
|
+
def generate_all_day_event
|
22
|
+
start_date = event.start_time || Time.now
|
23
|
+
end_date = event.end_time || (start_date + 86400)
|
24
|
+
|
20
25
|
params = {
|
21
26
|
v: 60,
|
22
27
|
view: 'd',
|
23
28
|
type: 20,
|
24
29
|
title: event.title,
|
25
|
-
st:
|
26
|
-
et:
|
30
|
+
st: format_date(start_date),
|
31
|
+
et: format_date(end_date),
|
27
32
|
desc: format_description,
|
28
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
|
+
|
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
|
+
|
45
|
+
description_parts = []
|
46
|
+
description_parts << format_description if format_description
|
47
|
+
description_parts << "Virtual Meeting URL: #{format_url}" if format_url
|
48
|
+
|
49
|
+
params = {
|
50
|
+
v: 60,
|
51
|
+
view: 'd',
|
52
|
+
type: 20,
|
53
|
+
title: event.title,
|
54
|
+
st: format_time(event.start_time),
|
55
|
+
et: format_time(event.end_time),
|
56
|
+
desc: description_parts.join("\n\n"),
|
57
|
+
in_loc: format_location,
|
29
58
|
crnd: event.timezone
|
30
59
|
}
|
31
60
|
|
@@ -33,16 +62,14 @@ module CalInvite
|
|
33
62
|
end
|
34
63
|
|
35
64
|
def generate_multi_day_event
|
36
|
-
# Yahoo doesn't support multi-day events in a single URL
|
37
|
-
# Return multiple URLs, one for each session
|
38
65
|
sessions = event.multi_day_sessions.map do |session|
|
39
66
|
params = {
|
40
67
|
v: 60,
|
41
68
|
view: 'd',
|
42
69
|
type: 20,
|
43
70
|
title: event.title,
|
44
|
-
st:
|
45
|
-
et:
|
71
|
+
st: format_time(session[:start_time]),
|
72
|
+
et: format_time(session[:end_time]),
|
46
73
|
desc: format_description,
|
47
74
|
in_loc: format_location,
|
48
75
|
crnd: event.timezone
|
@@ -54,12 +81,13 @@ module CalInvite
|
|
54
81
|
sessions.join("\n")
|
55
82
|
end
|
56
83
|
|
57
|
-
def
|
58
|
-
|
84
|
+
def format_date(time)
|
85
|
+
time.strftime("%Y%m%d")
|
59
86
|
end
|
60
87
|
|
61
|
-
def
|
62
|
-
|
88
|
+
def format_time(time)
|
89
|
+
# Always use UTC format for the URL, timezone is passed separately
|
90
|
+
time.utc.strftime("%Y%m%dT%H%M%SZ")
|
63
91
|
end
|
64
92
|
end
|
65
93
|
end
|
data/lib/cal_invite/providers.rb
CHANGED
@@ -1,15 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# lib/cal_invite/providers.rb
|
4
|
+
require_relative 'providers/base_provider'
|
5
|
+
|
4
6
|
module CalInvite
|
5
7
|
module Providers
|
6
|
-
SUPPORTED_PROVIDERS = %i[google ical outlook yahoo ics].freeze
|
8
|
+
SUPPORTED_PROVIDERS = %i[google ical outlook yahoo ics office365].freeze
|
7
9
|
|
8
|
-
autoload :BaseProvider, 'cal_invite/providers/base_provider'
|
9
10
|
autoload :Google, 'cal_invite/providers/google'
|
10
11
|
autoload :Ical, 'cal_invite/providers/ical'
|
11
12
|
autoload :Outlook, 'cal_invite/providers/outlook'
|
13
|
+
autoload :Office365, 'cal_invite/providers/office365'
|
12
14
|
autoload :Yahoo, 'cal_invite/providers/yahoo'
|
15
|
+
autoload :IcsContent, 'cal_invite/providers/ics_content'
|
16
|
+
autoload :IcsDownload, 'cal_invite/providers/ics_content'
|
13
17
|
autoload :Ics, 'cal_invite/providers/ics'
|
14
18
|
end
|
15
19
|
end
|
data/lib/cal_invite/version.rb
CHANGED
data/lib/cal_invite.rb
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
# lib/cal_invite.rb
|
2
|
+
require 'active_support'
|
3
|
+
require 'active_support/core_ext'
|
4
|
+
require 'securerandom'
|
5
|
+
require 'time'
|
6
|
+
require 'uri'
|
7
|
+
|
2
8
|
require 'cal_invite/version'
|
3
9
|
require 'cal_invite/configuration'
|
10
|
+
require 'cal_invite/caching'
|
4
11
|
require 'cal_invite/event'
|
5
12
|
require 'cal_invite/providers'
|
6
13
|
|
@@ -18,5 +25,8 @@ module CalInvite
|
|
18
25
|
def reset_configuration!
|
19
26
|
self.configuration = Configuration.new
|
20
27
|
end
|
28
|
+
|
29
|
+
# Include caching methods at the module level
|
30
|
+
include Caching
|
21
31
|
end
|
22
32
|
end
|
metadata
CHANGED
@@ -1,18 +1,31 @@
|
|
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.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephane Paquet
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-12-
|
12
|
-
dependencies:
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
date: 2024-12-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '6.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '6.0'
|
27
|
+
description: CalInvite provides a simple way to generate calendar invite URLs for
|
28
|
+
various providers (Google, Outlook, Yahoo) and ICS files
|
16
29
|
email:
|
17
30
|
- 176050+spaquet@users.noreply.github.com
|
18
31
|
executables: []
|
@@ -20,6 +33,7 @@ extensions: []
|
|
20
33
|
extra_rdoc_files: []
|
21
34
|
files:
|
22
35
|
- ".rubocop.yml"
|
36
|
+
- CACHING.md
|
23
37
|
- CHANGELOG.md
|
24
38
|
- CODE_OF_CONDUCT.md
|
25
39
|
- LICENSE.txt
|
@@ -29,6 +43,7 @@ files:
|
|
29
43
|
- gemfiles/Gemfile.rails7
|
30
44
|
- gemfiles/Gemfile.rails8
|
31
45
|
- lib/cal_invite.rb
|
46
|
+
- lib/cal_invite/caching.rb
|
32
47
|
- lib/cal_invite/configuration.rb
|
33
48
|
- lib/cal_invite/event.rb
|
34
49
|
- lib/cal_invite/providers.rb
|
@@ -36,6 +51,8 @@ files:
|
|
36
51
|
- lib/cal_invite/providers/google.rb
|
37
52
|
- lib/cal_invite/providers/ical.rb
|
38
53
|
- lib/cal_invite/providers/ics.rb
|
54
|
+
- lib/cal_invite/providers/ics_content.rb
|
55
|
+
- lib/cal_invite/providers/office365.rb
|
39
56
|
- lib/cal_invite/providers/outlook.rb
|
40
57
|
- lib/cal_invite/providers/yahoo.rb
|
41
58
|
- lib/cal_invite/version.rb
|
@@ -65,5 +82,5 @@ requirements: []
|
|
65
82
|
rubygems_version: 3.5.22
|
66
83
|
signing_key:
|
67
84
|
specification_version: 4
|
68
|
-
summary:
|
85
|
+
summary: A Ruby gem for generating calendar invite URLs and ICS files
|
69
86
|
test_files: []
|