Enough-Software / enough_icalendar

iCalendar library to parse, generate and respond to iCal / ics invites. Fully compliant with RFC 5545 (iCalendar) and RFC 5546 (iTIP).
Mozilla Public License 2.0
9 stars 9 forks source link

FormatException: Unknown component. #13

Closed vanlooverenkoen closed 9 months ago

vanlooverenkoen commented 9 months ago

Unable to parse ics from Formula 1

This is my ics url: https://ics.ecal.com/ecal-sub/65d5e5b6532bea0008b2ee55/Formula%201.ics

The error:

flutter: FormatException: Unknown component: BEGIN:VCALENDAR
PRODID:-//E-DIARY//E-DIARY 1.0//EN
VERSION:2.0
METHOD:PUBLISH
X-WR-CALNAME:Formula 1
X-Built-On-Cache-Miss:true
BEGIN:VEVENT
LAST-MODIFIED:20240221T122324Z
DTSTAMP:20240221T122324Z
LOCATION:Monaco
X-ECAL-SCHEDULE:63ed7aff4c410f0d70163027
DTSTART:20240524T113000Z
DTEND:20240524T123000Z
SUMMARY:🏎 FORMULA 1 GRAND PRIX DE MONACO 2024 - Practice 1
TRANSP:TRANSPARENT
SEQUENCE:0
UID:65af54f4f4b46e5026018b20
PRIORITY:5
X-MICROSOFT-CDO-IMPORTANCE:1
CLASS:PUBLIC
DESCRIPTION:Follow all the action on F1 TV and F1.com. \n\nUnlock the insid
 e world of F1 by signing up to F1 Unlocked at F1.com/F1Unlocked\n\nFollow 
 on the go with F1 TV\nhttps://cal.f1.com/f/pdgKx/2kxXC\n\nVisit the Race H
 ub\nhttps://cal.f1.com/f/pdgKF/2kxXC\n\nFree sign up to F1 Unlocked\nhttps
 ://cal.f1.com/f/pdgKP/2kxXC\n\nDownload the Official F1 Race Programme App
 \nhttps://cal.f1.com/f/pdgL1/2kxXC\n\nWear your favourite teams colours wi
 th F1 store\nhttps://cal.f1.com<…>

Stacktrace:

flutter: #0      VComponent._createComponent (package:enough_icalendar/src/components.dart:278:9)
#1      VComponent.parseLines (package:enough_icalendar/src/components.dart:210:18)
#2      VComponent.parse (package:enough_icalendar/src/components.dart:200:12)
#3      WebCalUtil.parseWebCal (package:impaktfull_f1/util/webcal/webcal_util.dart:90:34)
<asynchronous suspension>
#4      WebCalUtil.syncRacesWithWebCal (package:impaktfull_f1/util/webcal/webcal_util.dart:18:20)
<asynchronous suspension>
#5      _RaceRepository.syncRaces (package:impaktfull_f1/repo/race/race_repository.dart:161:28)
<asynchronous suspension>
#6      AdminViewModel.onSyncRacesTapped (package:impaktfull_f1/viewmodel/admin/admin_viewmodel.dart:29:22)
<asynchronous suspension>
#7      _ImpaktfullListItemState._onAsyncTap (package:impaktfull_ui/src/components/list_item/impaktfull_list_item.dart:70:7)
<asynchronous suspension>

This is my code to parse it.


  Future<List<WebCalEvent>> parseWebCal(String url) async {
    final httpUrl = url.replaceFirst('webcal://', 'https://');
    final data = await dio.get(httpUrl);
    if (data.statusCode != 200) {
      throw Exception('Failed to load webcal (${data.statusCode})');
    }
    final icsString = data.data as String;
    final icalendar = VComponent.parse(
      icsString,
    );
    if (icalendar is! VCalendar) throw Exception('Failed to parse webcal');
    final events = icalendar.children
        .map((element) {
          if (element is! VEvent) return null;
          final summary = element.summary;
          final startDate = element.start;
          final endDate = element.end;
          if (summary == null) return null;
          if (startDate == null) return null;
          if (endDate == null) return null;
          return WebCalEvent(
            title: summary,
            startDate: startDate,
            endDate: endDate,
          );
        })
        .whereType<WebCalEvent>()
        .toList();
    return events;
  }

class WebCalEvent {
  final String title;
  final DateTime startDate;
  final DateTime endDate;

  const WebCalEvent({
    required this.title,
    required this.startDate,
    required this.endDate,
  });
}
vanlooverenkoen commented 9 months ago

Looks like the parsing of the lines is incorrect. Because this is recognizes as 1 line:

BEGIN:VCALENDAR
PRODID:-//E-DIARY//E-DIARY 1.0//EN
VERSION:2.0
METHOD:PUBLISH
X-WR-CALNAME:Formula 1
X-Built-On-Cache-Miss:true
BEGIN:VEVENT
LAST-MODIFIED:20240221T122324Z
DTSTAMP:20240221T122324Z
LOCATION:Monaco
X-ECAL-SCHEDULE:63ed7aff4c410f0d70163027
DTSTART:20240524T113000Z
DTEND:20240524T123000Z
SUMMARY:🏎 FORMULA 1 GRAND PRIX DE MONACO 2024 - Practice 1
TRANSP:TRANSPARENT
SEQUENCE:0
UID:65af54f4f4b46e5026018b20
PRIORITY:5
X-MICROSOFT-CDO-IMPORTANCE:1
CLASS:PUBLIC
DESCRIPTION:Follow all the action on F1 TV and F1.com. \n\nUnlock the insid
 e world of F1 by signing up to F1 Unlocked at F1.com/F1Unlocked\n\nFollow 
 on the go with F1 TV\nhttps://cal.f1.com/f/pdgKx/2kxXC\n\nVisit the Race H
 ub\nhttps://cal.f1.com/f/pdgKF/2kxXC\n\nFree sign up to F1 Unlocked\nhttps
 ://cal.f1.com/f/pdgKP/2kxXC\n\nDownload the Official F1 Race Programme App
 \nhttps://cal.f1.com/f/pdgL1/2kxXC\n\nWear your favourite teams colours wi
 th F1 store\nhttps://cal.f1.com<…>
vanlooverenkoen commented 9 months ago

A found a workaround.

Important to not that this is a workaround and in the end should be fixed by this library.

I replaced:

    final icsString = data.data as String;
    final icalendar = VComponent.parse(
      icsString,
    );

with:

    final icsString = data.data as String;
    final formattedIcs = icsString.trim().split('\n').map((line) => line.trimRight()).join('\r\n');
    final icalendar = VComponent.parse(
      formattedIcs.trim(),
    );

trimRight is required otherwise the DESCRIPTION can not be parsed anymore.

robert-virkus commented 9 months ago

Thanks a lot, this is fixed in v0.16.0