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.
@@ -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.multi_day_sessions.any?
11
- generate_multi_day_event
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
- subject: event.title,
22
- startdt: format_time(event.start_time),
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
- if attendees_list.any?
31
- params[:to] = attendees_list.join(';')
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
- "#{BASE_URL}?#{URI.encode_www_form(params)}"
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 generate_multi_day_event
38
- # For Outlook, we create separate events for each session
39
- sessions = event.multi_day_sessions.map do |session|
40
- params = {
41
- subject: event.title,
42
- startdt: format_time(session[:start_time]),
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)}"
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
- sessions.join("\n")
70
+ params
58
71
  end
59
72
 
60
- def format_time(time)
61
- event.localize_time(time).strftime("%Y-%m-%dT%H:%M:%S")
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.multi_day_sessions.any?
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 generate_single_event
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: format_start_time(event.start_time),
26
- et: format_end_time(event.end_time),
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: format_start_time(session[:start_time]),
45
- et: format_end_time(session[:end_time]),
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 format_start_time(time)
58
- event.localize_time(time).strftime("%Y%m%dT%H%M%S")
84
+ def format_date(time)
85
+ time.strftime("%Y%m%d")
59
86
  end
60
87
 
61
- def format_end_time(time)
62
- event.localize_time(time).strftime("%Y%m%dT%H%M%S")
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
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CalInvite
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.3"
5
5
  end
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.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-17 00:00:00.000000000 Z
12
- dependencies: []
13
- description: CalInvite is a gem to generate calendar invitations and provides an easu
14
- way to generate .ics files or link to quickly save an event date/time into the major
15
- calendards such as Google Calendar, Microsoft Outlook, Apple Calendar and many more.
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: CalInvite is a gem to generate calendar invitations.
85
+ summary: A Ruby gem for generating calendar invite URLs and ICS files
69
86
  test_files: []