How to fix the meetup.com broken exported calendars.
I’m a big fan of meetup.com, but they’re so tragically unhip when it comes to mashups/integration/web 2.0. One of my biggest gripes until about 6 months ago was that they had no facility (besides API) for exporting a calendar of meetups to my calendar app (I use Google Calendar), or any other calendar app for that matter.
They introduced an export feature recently, but it’s pretty useless. Here’s why: they offer two calendars
- [Calendar A] contains all upcoming items in all your meetup groups
- [Calendar B] contains upcoming items which you have RSVP’d with “yes” or “maybe”.
That’s it. The calendars exported don’t even contain links that allow you to RSVP from directly inside your calendar — you have click through to the meetup.com site, log in, then RSVP. Ugh.
Come on, product guys. What’s really called for is 4 separate calendars.
- [Calendar "yes"] All groups, “yes” events
- [Calendar "maybe"] All groups, “maybe” events
- [Calendar "no"] All groups, “no” events
- [Calendar "none"] All groups, events to which I have not yet submitted an RSVP.
I was finally just pissed off enough about the status quo that I fixed it for myself, and below I share the code. You can try it out here: http://spicylogic.com/allenday/cgi-bin/mu.cgi?key=<your_api_key>&cal=<calendar>
where <your_api_key> can be found here and <calendar> is one of “yes”, “no”, “none”, “maybe”.
Okay, here’s the code. Install it on your own machine if possible, my ISP will appreciate it. If you find fuckups, let me know and I’ll update the post.
#!/usr/bin/perl use strict; use CGI qw(:standard); use Date::Manip qw(ParseDate ParseDateString ParseDateDelta DateCalc UnixDate); use Date::Parse; use HTML::Entities; use LWP::Simple qw(get); use XML::DOM; use constant URL_EVENTS => 'http://api.meetup.com/events?key=%s&member_id=%d&format=xml'; print header(q(text/calendar)); my $parser = new XML::DOM::Parser (); my $mode = param( 'cal' ); my $key = param( 'key' ); my $user = param( 'user' ); if ( ! $mode || ! $key || ! $user ) { die } my $events_url = sprintf( URL_EVENTS, $key, $user ); #warn $events_url; my $events_txt = get( $events_url ); #warn $events_txt; my $events_dom = $parser->parse( $events_txt ); #warn $events_dom; print qq(BEGIN:VCALENDAR\nPRODID:-//Meetup Inc//RemoteApi//EN\nVERSION:2.0\nMETHOD:PUBLISH\nCALSCALE:GREGORIAN\nX-ORIGINAL-URL:http://www.meetup.com/\nX-WR-CALNAME:mu $mode\n); my $events = $events_dom->getElementsByTagName( 'item' ); for ( my $i = 0 ; $i < $events->getLength() ; $i++ ) { my $event = $events->item( $i ); my $n_id = $event->getElementsByTagName( 'id' )->item( 0 )->getFirstChild(); my $n_rsvp = $event->getElementsByTagName( 'myrsvp' )->item( 0 )->getFirstChild(); my $n_addr0 = $event->getElementsByTagName( 'venue_name' )->item( 0 )->getFirstChild(); my $n_addr1 = $event->getElementsByTagName( 'venue_address1' )->item( 0 )->getFirstChild(); my $n_addr2 = $event->getElementsByTagName( 'venue_address2' )->item( 0 )->getFirstChild(); my $n_addr3 = $event->getElementsByTagName( 'venue_address3' )->item( 0 )->getFirstChild(); my $n_addr4 = $event->getElementsByTagName( 'venue_city' )->item( 0 )->getFirstChild(); my $n_addr5 = $event->getElementsByTagName( 'venue_state' )->item( 0 )->getFirstChild(); my $n_addr6 = $event->getElementsByTagName( 'venue_zip' )->item( 0 )->getFirstChild(); my $n_desc = $event->getElementsByTagName( 'description' )->item( 0 )->getFirstChild(); my $n_link = $event->getElementsByTagName( 'event_url' )->item( 0 )->getFirstChild(); my $n_name = $event->getElementsByTagName( 'name' )->item( 0 )->getFirstChild(); my $n_lat = $event->getElementsByTagName( 'venue_lat' )->item( 0 )->getFirstChild(); my $n_lon = $event->getElementsByTagName( 'venue_lon' )->item( 0 )->getFirstChild(); my $n_start_time = $event->getElementsByTagName( 'time' )->item( 0 )->getFirstChild(); my $start_time; my $end_time; #my $dummy_time = "20000101T000000Z"; my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time()); my $dummy_time = sprintf( q(%04d%02d%02dT%02d%02d%02dZ), $year + 1900, $mon + 1, $mday, $hour, $min, $sec ); if ( $n_start_time ) { my ($ss,$mm,$hh,$day,$month,$year,$zone); ($ss,$mm,$hh,$day,$month,$year,$zone) = strptime( $n_start_time->toString() ); $start_time = sprintf( q(%04d%02d%02dT%02d%02d%02dZ), $year + 1900, $month + 1, $day, $hh, $mm, $ss ); my $eday = $day; if ( $hh == 23 ) { $eday = $day + 1; } my $ehh = ($hh + 1) % 24; $end_time = sprintf( q(%04d%02d%02dT%02d%02d%02dZ), $year + 1900, $month + 1, $eday, $ehh, $mm, $ss ); } else { $start_time = ''; $end_time = ''; } if ( $mode eq $n_rsvp->toString() ) { my $id = $n_id->toString(); my $name = $n_name ? $n_name->toString() : ""; my $desc = $n_desc ? $n_desc->toString() : ""; my $addr = ( $n_addr0 ? $n_addr0->toString().', ' : "" ) . ( $n_addr1 ? $n_addr1->toString().', ' : "" ) . ( $n_addr2 ? $n_addr2->toString().', ' : "" ) . ( $n_addr3 ? $n_addr3->toString().', ' : "" ) . ( $n_addr4 ? $n_addr4->toString().', ' : "" ) . ( $n_addr5 ? $n_addr5->toString().', ' : "" ) . ( $n_addr6 ? $n_addr6->toString() : "" ); #$desc =~ s/(.)/(ord($1) > 127) ? "" : $1/egs; $name = HTML::Entities::decode_entities( $name ); $desc = HTML::Entities::decode_entities( $desc ); $addr = HTML::Entities::decode_entities( $addr ); $name =~ s/,/\\,/g; $desc =~ s/,/\\,/g; $addr =~ s/,/\\,/g; $desc =~ s# #\\n#gs; $desc .= "\\n\\n\\nGoing?\\n\\n"; foreach my $response ( qw( yes no maybe ) ) { $desc .= uc($response).qq(: http://api.meetup.com/rsvp?event_id=$id&key=$key&rsvp=$response\\n); } my $geo = $n_lat && $n_lon ? "GEO:" . $n_lat->toString() . ";" . $n_lon->toString() . "\n" : undef; #print sprintf( qq(BEGIN:VEVENT\nSUMMARY:%s\nDESCRIPTION:%s\nLAST-MODIFIED:%s\nUID:%s\nCLASS:%s\nCREATED:%s\nDTSTAMP:%s\nDTSTART:%s\nDTEND:%s\nLOCATION:%s\n\nURL:%s\nEND:VEVENT\n), print sprintf( qq(BEGIN:VEVENT\nSUMMARY:%s\nDESCRIPTION:%s\nLAST-MODIFIED:%s\nUID:%s\nCLASS:%s\nCREATED:%s\nDTSTAMP:%s\nDTSTART:%s\nDTEND:%s\n%sLOCATION:%s\nURL:%s\nEND:VEVENT\n), $name, $desc, $start_time, "event_$id\@meetup.com", "PUBLIC", $dummy_time, $dummy_time, $start_time, $end_time, $geo, $addr, $n_link ? $n_link->toString() : "", ); } } print qq(END:VCALENDAR\n);