diff --git a/README.md b/README.md index 038c97d..bb8dbe5 100755 --- a/README.md +++ b/README.md @@ -41,6 +41,9 @@ Or install it yourself as: @cal.yahoo_url #=> "https://calendar.yahoo.com/?v=60&view=d&type=20&title=Christmas%20party%21&st=20201212T133000Z&dur=0100" +@cal.hey_url +#=> "https://app.hey.com/calendar/ical_events/new?ical_source=BEGIN%3AVCALENDAR%0AVERSION%3A2.0%0APRODID%3A-//AddToCalendar//RubyGem//EN%0ABEGIN%3AVEVENT%0ASUMMARY%3AHolly%27s%209th%20birthday%21%0ADTSTAMP%3A20240913T151029Z%0ADTSTART%3A20240906T123000Z%0ADTEND%3A20240906T133000Z%0AUID%3A-20240906T123000Z-Holly%27s%209th%20birthday%21%0AEND%3AVEVENT%0AEND%3AVCALENDAR" + @cal.office365_url #=> "https://outlook.office.com/calendar/0/deeplink/compose?path=/calendar/action/compose&rru=addevent&subject=Christmas%20party%21&startdt=2020-12-12T13:30:00Z&enddt=2020-12-12T14:30:00Z" diff --git a/lib/add_to_calendar.rb b/lib/add_to_calendar.rb index eba1240..6536c04 100755 --- a/lib/add_to_calendar.rb +++ b/lib/add_to_calendar.rb @@ -92,6 +92,59 @@ def yahoo_url return calendar_url end + def hey_url + calendar_url = "https://app.hey.com/calendar/ical_events/new?ical_source=BEGIN%3AVCALENDAR%0AVERSION%3A2.0%0APRODID%3A-//AddToCalendar//RubyGem//EN%0ABEGIN%3AVEVENT" + + params = {} + params[:SUMMARY] = url_encode(title) + params[:DTSTAMP] = Time.now.strftime("%Y%m%dT%H%M%SZ") + + if all_day + one_day = 1 * 24 * 60 * 60 + params["DTSTART%3BVALUE=DATE"] = format_date(start_datetime) + if end_datetime + params["DTEND%3BVALUE=DATE"] = format_date(end_datetime + one_day) + else + params["DTEND%3BVALUE=DATE"] = format_date(start_datetime + one_day) + end + else + params[:DTSTART] = utc_datetime(start_datetime) + if end_datetime + params[:DTEND] = utc_datetime(end_datetime) + else + params[:DTEND] = utc_datetime(start_datetime + 60*60) # 1 hour later + end + end + + params[:URL] = url_encode(url) if url + params[:UID] = "-#{utc_datetime(start_datetime)}-#{url_encode(title)}" + params[:DESCRIPTION] = url_encode_hey(description) if description + if add_url_to_description && url + if params[:DESCRIPTION] + params[:DESCRIPTION] << "\n\n#{url_encode(url)}" + else + params[:DESCRIPTION] = url_encode(url) + end + end + if organizer + params[:ORGANIZER] = url_encode("CN=\"#{organizer[:name]}\"%3Amailto%3A#{organizer[:email]}") + end + params[:LOCATION] = url_encode(location) if location + + new_line = "%0A" + params.each do |key, value| + if key == :ORGANIZER + calendar_url << "#{new_line}#{key}%3B#{value}" + else + calendar_url << "#{new_line}#{key}%3A#{value}" + end + end + + calendar_url << "%0AEND%3AVEVENT%0AEND%3AVCALENDAR" + + return calendar_url + end + def office365_url # Eg. https://outlook.live.com/calendar/0/deeplink/compose?path=/calendar/action/compose&rru=addevent&subject=Holly%27s%208th%20Birthday%21&startdt=2020-05-12T12:30:00Z&enddt=2020-05-12T16:00:00Z&body=Come%20join%20us%20for%20lots%20of%20fun%20%26%20cake%21%0A%0Ahttps%3A%2F%2Fwww.example.com%2Fevent-details&location=Flat%204%2C%20The%20Edge%2C%2038%20Smith-Dorrien%20St%2C%20London%2C%20N1%207GU microsoft("office365") @@ -326,6 +379,17 @@ def url_encode_ical(s) }.join("\\n") end + def url_encode_hey(s) + string = s.dup # don't modify original input + string.split("\n").map { |e| + if e.empty? + e + else + url_encode(e) + end + }.join("\n") + end + def yahoo_param(key) if key == :in_loc key.to_s diff --git a/test/support/assertions/hey_url_assertions.rb b/test/support/assertions/hey_url_assertions.rb new file mode 100644 index 0000000..ee7d02b --- /dev/null +++ b/test/support/assertions/hey_url_assertions.rb @@ -0,0 +1,68 @@ +require 'uri' +require 'cgi' + +module Minitest::Assertions + def assert_hey_urls_equal(expected, actual, msg = nil) + differences = compare_hey_urls(expected, actual) + + if differences.empty? + assert true # URLs are identical + else + failure_message = build_failure_message(differences) + msg = message(msg) { failure_message } + assert false, msg + end + end + + private + + def decode_ical_source(url) + uri = URI(url) + CGI.unescape(URI.decode_www_form(uri.query).to_h['ical_source']).split("\n") + end + + def compare_hey_urls(url1, url2) + ical1 = decode_ical_source(url1) + ical2 = decode_ical_source(url2) + + differences = [] + + # Compare each line of the decoded ical_source + ical1.zip(ical2).each_with_index do |(line1, line2), index| + if line1 != line2 + differences << { + line_number: index + 1, + expected_line: line2, + actual_line: line1 + } + end + end + + # Handle case where one ical_source is longer than the other + if ical1.length != ical2.length + start = [ical1.length, ical2.length].min + longer = ical1.length > ical2.length ? ical1 : ical2 + longer_label = ical1.length > ical2.length ? 'actual' : 'expected' + + longer[start..-1].each_with_index do |line, i| + differences << { + line_number: start + i + 1, + "#{longer_label}_line": line, + "#{longer_label == 'actual' ? 'expected_line' : 'actual_line'}": nil + } + end + end + + differences + end + + def build_failure_message(differences) + message = "Hey URLs do not match. Differences found:\n" + differences.each do |diff| + message << "Line #{diff[:line_number]}:\n" + message << " Expected: #{diff[:expected_line]}\n" + message << " Actual: #{diff[:actual_line]}\n\n" + end + message + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 4e9def7..5435002 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -3,3 +3,5 @@ require "minitest/autorun" require "timecop" require "pry" + +Dir[File.expand_path('support/**/*.rb', __dir__)].each { |file| require file } diff --git a/test/urls/hey_url_test.rb b/test/urls/hey_url_test.rb new file mode 100644 index 0000000..67dfb97 --- /dev/null +++ b/test/urls/hey_url_test.rb @@ -0,0 +1,488 @@ +require "test_helper" + +class HeyUrlTest < Minitest::Test + def setup + # We need to freeze time on each test because DTSTAMP is generated via Time.now + Timecop.freeze(Time.now.utc) + end + + def teardown + Timecop.return + end + + def test_without_end_datetime_ends_one_hour_after_start + event = TestEvent.new( + start_datetime: Time.new(2024,9,6,12,30,00,0), + title: "Holly's 9th birthday!", + timezone: "UTC" + ) + + cal = AddToCalendar::URLs.new( + start_datetime: event.start_datetime, + title: event.title, + timezone: event.timezone + ) + + expected = "https://app.hey.com/calendar/ical_events/new" + + "?ical_source=BEGIN%3AVCALENDAR%0AVERSION%3A2.0" + + "%0APRODID%3A-//AddToCalendar//RubyGem//EN%0ABEGIN%3AVEVENT" + + "%0ASUMMARY%3A#{event.title_encoded}" + + "%0ADTSTAMP%3A#{event.dtstamp}" + + "%0ADTSTART%3A#{event.dtstart}" + + "%0ADTEND%3A#{event.dtstart_plus_one_hour}" + + event.uid + + "%0AEND%3AVEVENT%0AEND%3AVCALENDAR" + + assert_hey_urls_equal cal.hey_url, expected + end + + def test_with_end_datetime + event = TestEvent.new( + start_datetime: Time.new(2024,9,6,12,30,00,0), + end_datetime: Time.new(2024,9,6,15,30,00,0), + title: "Holly's 9th birthday!", + timezone: "UTC" + ) + + cal = AddToCalendar::URLs.new( + start_datetime: event.start_datetime, + end_datetime: event.end_datetime, + title: event.title, + timezone: event.timezone + ) + + expected = "https://app.hey.com/calendar/ical_events/new" + + "?ical_source=BEGIN%3AVCALENDAR%0AVERSION%3A2.0" + + "%0APRODID%3A-//AddToCalendar//RubyGem//EN%0ABEGIN%3AVEVENT" + + "%0ASUMMARY%3A#{event.title_encoded}" + + "%0ADTSTAMP%3A#{event.dtstamp}" + + "%0ADTSTART%3A#{event.dtstart}" + + "%0ADTEND%3A#{event.dtend}" + + event.uid + + "%0AEND%3AVEVENT%0AEND%3AVCALENDAR" + + assert_hey_urls_equal cal.hey_url, expected + end + + def test_with_end_datetime_after_midnight + event = TestEvent.new( + start_datetime: Time.new(2024,9,6,23,30,00,0), + end_datetime: Time.new(2024,9,7,1,30,00,0), + title: "Holly's 19th birthday!", + timezone: "UTC" + ) + + cal = AddToCalendar::URLs.new( + start_datetime: event.start_datetime, + end_datetime: event.end_datetime, + title: event.title, + timezone: event.timezone + ) + + expected = "https://app.hey.com/calendar/ical_events/new" + + "?ical_source=BEGIN%3AVCALENDAR%0AVERSION%3A2.0" + + "%0APRODID%3A-//AddToCalendar//RubyGem//EN%0ABEGIN%3AVEVENT" + + "%0ASUMMARY%3A#{event.title_encoded}" + + "%0ADTSTAMP%3A#{event.dtstamp}" + + "%0ADTSTART%3A#{event.dtstart}" + + "%0ADTEND%3A#{event.dtend}" + + event.uid + + "%0AEND%3AVEVENT%0AEND%3AVCALENDAR" + + assert_hey_urls_equal cal.hey_url, expected + end + + def test_with_location + event = TestEvent.new( + start_datetime: Time.new(2024,9,6,12,30,00,0), + end_datetime: Time.new(2024,9,6,15,30,00,0), + title: "Holly's 9th birthday!", + timezone: "UTC", + location: "Address, Postcode" + ) + + cal = AddToCalendar::URLs.new( + start_datetime: event.start_datetime, + end_datetime: event.end_datetime, + title: event.title, + timezone: event.timezone, + location: event.location + ) + + expected = "https://app.hey.com/calendar/ical_events/new" + + "?ical_source=BEGIN%3AVCALENDAR%0AVERSION%3A2.0" + + "%0APRODID%3A-//AddToCalendar//RubyGem//EN%0ABEGIN%3AVEVENT" + + "%0ASUMMARY%3A#{event.title_encoded}" + + "%0ADTSTAMP%3A#{event.dtstamp}" + + "%0ADTSTART%3A#{event.dtstart}" + + "%0ADTEND%3A#{event.dtend}" + + event.uid + + "%0ALOCATION%3A#{event.location_encoded}" + + "%0AEND%3AVEVENT%0AEND%3AVCALENDAR" + + assert_hey_urls_equal cal.hey_url, expected + end + + def test_with_url_without_description + event = TestEvent.new( + start_datetime: Time.new(2024,9,6,12,30,00,0), + end_datetime: Time.new(2024,9,6,15,30,00,0), + title: "Holly's 9th birthday!", + timezone: "UTC", + url: "https://www.example.com/event-details" + ) + + cal = AddToCalendar::URLs.new( + start_datetime: event.start_datetime, + end_datetime: event.end_datetime, + title: event.title, + timezone: event.timezone, + url: event.url + ) + + expected = "https://app.hey.com/calendar/ical_events/new" + + "?ical_source=BEGIN%3AVCALENDAR%0AVERSION%3A2.0" + + "%0APRODID%3A-//AddToCalendar//RubyGem//EN%0ABEGIN%3AVEVENT" + + "%0ASUMMARY%3A#{event.title_encoded}" + + "%0ADTSTAMP%3A#{event.dtstamp}" + + "%0ADTSTART%3A#{event.dtstart}" + + "%0ADTEND%3A#{event.dtend}" + + "%0AURL%3A#{event.url_encoded}" + + event.uid + + "%0ADESCRIPTION%3A#{event.url_encoded}" + + "%0AEND%3AVEVENT%0AEND%3AVCALENDAR" + + assert_hey_urls_equal cal.hey_url, expected + end + + def test_with_url_and_description + event = TestEvent.new( + start_datetime: Time.new(2024,9,6,12,30,00,0), + end_datetime: Time.new(2024,9,6,15,30,00,0), + title: "Holly's 9th birthday!", + timezone: "UTC", + url: "https://www.example.com/event-details", + description: "Come join us for lots of fun & cake!" + ) + + cal = AddToCalendar::URLs.new( + start_datetime: event.start_datetime, + end_datetime: event.end_datetime, + title: event.title, + timezone: event.timezone, + url: event.url, + description: event.description + ) + + expected = "https://app.hey.com/calendar/ical_events/new" + + "?ical_source=BEGIN%3AVCALENDAR%0AVERSION%3A2.0" + + "%0APRODID%3A-//AddToCalendar//RubyGem//EN%0ABEGIN%3AVEVENT" + + "%0ASUMMARY%3A#{event.title_encoded}" + + "%0ADTSTAMP%3A#{event.dtstamp}" + + "%0ADTSTART%3A#{event.dtstart}" + + "%0ADTEND%3A#{event.dtend}" + + "%0AURL%3A#{event.url_encoded}" + + event.uid + + "%0ADESCRIPTION%3A#{event.description_encoded}\n\n#{event.url_encoded}" + + "%0AEND%3AVEVENT%0AEND%3AVCALENDAR" + + assert_hey_urls_equal cal.hey_url, expected + end + + def test_description_with_newlines + event = TestEvent.new( + start_datetime: Time.new(2024,9,6,12,30,00,0), + end_datetime: Time.new(2024,9,6,15,30,00,0), + title: "Holly's 9th birthday!", + timezone: "UTC", + description: "Test\n\nNewline" + ) + + cal = AddToCalendar::URLs.new( + start_datetime: event.start_datetime, + end_datetime: event.end_datetime, + title: event.title, + timezone: event.timezone, + url: event.url, + description: event.description + ) + + expected = "https://app.hey.com/calendar/ical_events/new" + + "?ical_source=BEGIN%3AVCALENDAR%0AVERSION%3A2.0" + + "%0APRODID%3A-//AddToCalendar//RubyGem//EN%0ABEGIN%3AVEVENT" + + "%0ASUMMARY%3A#{event.title_encoded}" + + "%0ADTSTAMP%3A#{event.dtstamp}" + + "%0ADTSTART%3A#{event.dtstart}" + + "%0ADTEND%3A#{event.dtend}" + + event.uid + + "%0ADESCRIPTION%3A#{"Test\n\nNewline"}" + + "%0AEND%3AVEVENT%0AEND%3AVCALENDAR" + + assert_hey_urls_equal cal.hey_url, expected + end + + def test_add_url_to_description_false_without_url + event = TestEvent.new( + start_datetime: Time.new(2024,9,6,12,30,00,0), + end_datetime: Time.new(2024,9,6,15,30,00,0), + title: "Holly's 9th birthday!", + timezone: "UTC" + ) + + cal = AddToCalendar::URLs.new( + start_datetime: event.start_datetime, + end_datetime: event.end_datetime, + title: event.title, + timezone: event.timezone, + add_url_to_description: false + ) + + expected = "https://app.hey.com/calendar/ical_events/new" + + "?ical_source=BEGIN%3AVCALENDAR%0AVERSION%3A2.0" + + "%0APRODID%3A-//AddToCalendar//RubyGem//EN%0ABEGIN%3AVEVENT" + + "%0ASUMMARY%3A#{event.title_encoded}" + + "%0ADTSTAMP%3A#{event.dtstamp}" + + "%0ADTSTART%3A#{event.dtstart}" + + "%0ADTEND%3A#{event.dtend}" + + event.uid + + "%0AEND%3AVEVENT%0AEND%3AVCALENDAR" + + assert_hey_urls_equal cal.hey_url, expected + end + + def test_add_url_to_description_false_with_url + event = TestEvent.new( + start_datetime: Time.new(2024,9,6,12,30,00,0), + end_datetime: Time.new(2024,9,6,15,30,00,0), + title: "Holly's 9th birthday!", + timezone: "UTC", + url: "https://www.example.com/event-details" + ) + + cal = AddToCalendar::URLs.new( + start_datetime: event.start_datetime, + end_datetime: event.end_datetime, + title: event.title, + timezone: event.timezone, + url: event.url, + add_url_to_description: false + ) + + expected = "https://app.hey.com/calendar/ical_events/new" + + "?ical_source=BEGIN%3AVCALENDAR%0AVERSION%3A2.0" + + "%0APRODID%3A-//AddToCalendar//RubyGem//EN%0ABEGIN%3AVEVENT" + + "%0ASUMMARY%3A#{event.title_encoded}" + + "%0ADTSTAMP%3A#{event.dtstamp}" + + "%0ADTSTART%3A#{event.dtstart}" + + "%0ADTEND%3A#{event.dtend}" + + "%0AURL%3A#{event.url_encoded}" + + event.uid + + "%0AEND%3AVEVENT%0AEND%3AVCALENDAR" + + assert_hey_urls_equal cal.hey_url, expected + end + + def test_with_all_attributes + event = TestEvent.new( + start_datetime: Time.new(2024,9,6,12,30,00,0), + end_datetime: Time.new(2024,9,6,15,30,00,0), + title: "Holly's 9th birthday!", + timezone: "UTC", + location: "Address, Postcode", + url: "https://www.example.com/event-details", + description: "Come join us for lots of fun & cake!" + ) + + cal = AddToCalendar::URLs.new( + start_datetime: event.start_datetime, + end_datetime: event.end_datetime, + title: event.title, + timezone: event.timezone, + url: event.url, + location: event.location, + description: event.description, + ) + + expected = "https://app.hey.com/calendar/ical_events/new" + + "?ical_source=BEGIN%3AVCALENDAR%0AVERSION%3A2.0" + + "%0APRODID%3A-//AddToCalendar//RubyGem//EN%0ABEGIN%3AVEVENT" + + "%0ASUMMARY%3A#{event.title_encoded}" + + "%0ADTSTAMP%3A#{event.dtstamp}" + + "%0ADTSTART%3A#{event.dtstart}" + + "%0ADTEND%3A#{event.dtend}" + + "%0AURL%3A#{event.url_encoded}" + + event.uid + + "%0ADESCRIPTION%3A#{event.description_encoded}\n\n#{event.url_encoded}" + + "%0ALOCATION%3A#{event.location_encoded}" + + "%0AEND%3AVEVENT%0AEND%3AVCALENDAR" + + assert_hey_urls_equal cal.hey_url, expected + end + + def test_all_day_spans_single_day + event = TestEvent.new( + start_datetime: Time.new(2024,9,6,12,30,00,0), + end_datetime: Time.new(2024,9,6,15,30,00,0), + title: "Holly's 9th birthday!", + timezone: "UTC" + ) + + cal = AddToCalendar::URLs.new( + start_datetime: event.start_datetime, + end_datetime: event.end_datetime, + title: event.title, + timezone: event.timezone, + all_day: true + ) + + expected = "https://app.hey.com/calendar/ical_events/new" + + "?ical_source=BEGIN%3AVCALENDAR%0AVERSION%3A2.0" + + "%0APRODID%3A-//AddToCalendar//RubyGem//EN%0ABEGIN%3AVEVENT" + + "%0ASUMMARY%3A#{event.title_encoded}" + + "%0ADTSTAMP%3A#{event.dtstamp}" + + "%0ADTSTART%3BVALUE%3DDATE%3A#{"20240906"}" + + "%0ADTEND%3BVALUE%3DDATE%3A#{"20240907"}" + + event.uid + + "%0AEND%3AVEVENT%0AEND%3AVCALENDAR" + + assert_hey_urls_equal cal.hey_url, expected + end + + def test_all_day_spans_multiple_days + event = TestEvent.new( + start_datetime: Time.new(2024,9,6,12,30,00,0), + end_datetime: Time.new(2024,9,13,15,30,00,0), + title: "Holly's 9th birthday!", + timezone: "UTC" + ) + + cal = AddToCalendar::URLs.new( + start_datetime: event.start_datetime, + end_datetime: event.end_datetime, + title: event.title, + timezone: event.timezone, + all_day: true + ) + + expected = "https://app.hey.com/calendar/ical_events/new" + + "?ical_source=BEGIN%3AVCALENDAR%0AVERSION%3A2.0" + + "%0APRODID%3A-//AddToCalendar//RubyGem//EN%0ABEGIN%3AVEVENT" + + "%0ASUMMARY%3A#{event.title_encoded}" + + "%0ADTSTAMP%3A#{event.dtstamp}" + + "%0ADTSTART%3BVALUE%3DDATE%3A#{"20240906"}" + + "%0ADTEND%3BVALUE%3DDATE%3A#{"20240914"}" + + event.uid + + "%0AEND%3AVEVENT%0AEND%3AVCALENDAR" + + assert_hey_urls_equal cal.hey_url, expected + end + + def test_all_day_without_end_date_is_single_day + event = TestEvent.new( + start_datetime: Time.new(2024,9,6,12,30,00,0), + title: "Holly's 9th birthday!", + timezone: "UTC" + ) + + cal = AddToCalendar::URLs.new( + start_datetime: event.start_datetime, + title: event.title, + timezone: event.timezone, + all_day: true + ) + + expected = "https://app.hey.com/calendar/ical_events/new" + + "?ical_source=BEGIN%3AVCALENDAR%0AVERSION%3A2.0" + + "%0APRODID%3A-//AddToCalendar//RubyGem//EN%0ABEGIN%3AVEVENT" + + "%0ASUMMARY%3A#{event.title_encoded}" + + "%0ADTSTAMP%3A#{event.dtstamp}" + + "%0ADTSTART%3BVALUE%3DDATE%3A#{"20240906"}" + + "%0ADTEND%3BVALUE%3DDATE%3A#{"20240907"}" + + event.uid + + "%0AEND%3AVEVENT%0AEND%3AVCALENDAR" + + assert_hey_urls_equal cal.hey_url, expected + end + + def test_all_day_end_date_is_plus_one_from_end_date + event = TestEvent.new( + start_datetime: Time.new(2024,9,6,12,30,00,0), + end_datetime: Time.new(2024,9,6,15,30,00,0), + title: "Holly's 9th birthday!", + timezone: "UTC" + ) + + cal = AddToCalendar::URLs.new( + start_datetime: event.start_datetime, + end_datetime: event.end_datetime, + title: event.title, + timezone: event.timezone, + all_day: true + ) + + expected = "https://app.hey.com/calendar/ical_events/new" + + "?ical_source=BEGIN%3AVCALENDAR%0AVERSION%3A2.0" + + "%0APRODID%3A-//AddToCalendar//RubyGem//EN%0ABEGIN%3AVEVENT" + + "%0ASUMMARY%3A#{event.title_encoded}" + + "%0ADTSTAMP%3A#{event.dtstamp}" + + "%0ADTSTART%3BVALUE%3DDATE%3A#{"20240906"}" + + "%0ADTEND%3BVALUE%3DDATE%3A#{"20240907"}" + + event.uid + + "%0AEND%3AVEVENT%0AEND%3AVCALENDAR" + + assert_hey_urls_equal cal.hey_url, expected + end + + private + + class TestEvent + attr_reader :start_datetime, :end_datetime, :title, :timezone, :location, :url, :description + + def initialize(start_datetime:, end_datetime: nil, title:, timezone:, location: nil, url: nil, description: nil) + @start_datetime = start_datetime + @end_datetime = end_datetime + @title = title + @timezone = timezone + @location = location + @url = url + @description = description + end + + def dtstart + @start_datetime.utc.strftime('%Y%m%dT%H%M%SZ') + end + + def dtstart_plus_one_hour + one_hour = 60*60 + (@start_datetime + one_hour).utc.strftime('%Y%m%dT%H%M%SZ') + end + + def dtend + @end_datetime.utc.strftime('%Y%m%dT%H%M%SZ') + end + + def uid + "%0AUID%3A-#{url_encode(dtstart)}-#{url_encode(@title)}" + end + + def dtstamp + Time.now.utc.strftime('%Y%m%dT%H%M%SZ') + end + + def title_encoded + url_encode(@title) + end + + def location_encoded + url_encode(@location) + end + + def url_encoded + url_encode(@url) + end + + def description_encoded + url_encode(@description) + end + end +end