root/misc/30boxes.pl

Revision 2948 (checked in by miyagawa, 10 years ago)

use env perl

  • Property svn:executable set to *
Line 
1 #!/usr/bin/env perl
2 use strict;
3 use warnings;
4
5 =head1 DESCRIPTION
6
7 This is a simple command-line interface to 30boxes that can be used
8 like Lifehacker.com's todo.sh script.
9
10 =cut
11
12 use Date::Manip;
13 use Encode;
14 use ExtUtils::MakeMaker ();
15 use File::HomeDir;
16 use File::Spec;
17 use Getopt::Long;
18 use YAML;
19 use LWP::UserAgent;
20 use Pod::Usage;
21 use URI;
22 use XML::Simple;
23
24 our $conf = File::Spec->catfile(File::HomeDir->my_home, ".30boxes");
25 our $ua = LWP::UserAgent->new;
26 our %config = ();
27 our %args   = ();
28 our $changed;
29
30 $ua->env_proxy;
31
32 main();
33
34 END {
35     save_config() if $changed;
36 }
37
38 sub prompt {
39     my $value = ExtUtils::MakeMaker::prompt($_[0]);
40     $changed++;
41     return $value;
42 }
43
44 sub setup_encoding {
45     my $encoding;
46     eval {
47         require Term::Encoding;
48         $encoding = Term::Encoding::get_encoding();
49     };
50     $encoding ||= "utf-8";
51     binmode STDOUT, ":encoding($encoding)";
52     binmode STDIN, ":encoding($encoding)";
53     @ARGV = map decode($encoding, $_), @ARGV;
54 }
55
56 sub main {
57     setup_encoding();
58     GetOptions(\%args,
59                "start=s",
60                "from=s",
61                "end=s",
62                "to=s",
63                "month=s",
64                "date=s",
65                "help",
66                "config=s")
67         or pod2usage(2);
68
69     $conf = $args{config} if $args{config};
70     pod2usage(0) if $args{help};
71
72     # alias from/start, to/end
73     $args{start} ||= $args{from};
74     $args{end}   ||= $args{to};
75
76     # Human readable one
77     $args{start} = parse_date($args{start}) if $args{start};
78     $args{end}   = parse_date($args{end})   if $args{end};
79
80     # map month to start/end
81     if ($args{month}) {
82         my $target = parse_date($args{month});
83         my($year, $month, $day) = split /-/, $target;
84         my $end = Date_DaysInMonth($month, $year);
85         $args{start} = "$year-$month-1";
86         $args{end}   = "$year-$month-$end";
87     }
88
89     $args{date} = parse_date($args{date}) if $args{date};
90
91     setup_config();
92
93     my %commands = (
94         list => \&list_events,
95         add  => \&add_event,
96         del  => \&delete_event,
97         rm   => \&delete_event,
98 #        update => \&update_event,
99     );
100
101     my $command = shift @ARGV || "list";
102     $commands{$command} or pod2usage(-message => "Unknown command: $command", -exitval => 2);
103     $commands{$command}->();
104 }
105
106 sub parse_date {
107     my $time = UnixDate(ParseDateString($_[0]), "%s") or die "Can't parse '$_[0]' as a date string\n";
108     my @date = localtime($time);
109
110     return join '-', $date[5] + 1900, $date[4] + 1, $date[3];
111 }
112
113 sub setup_config {
114     my $config = eval { YAML::LoadFile($conf) } || {};
115     %config = %$config;
116     $config{apikey}     ||= prompt("30boxes API Key:");
117     $config{auth_token} ||= prompt(<<PROMPT);
118 You need to login 30boxes to authorize this app.
119 Go to the following URL and paste the result token here.
120   http://30boxes.com/api/api.php?method=user.Authorize&apiKey=$config{apikey}&applicationName=30boxes
121 Your token:
122 PROMPT
123 }
124
125 sub save_config {
126     YAML::DumpFile($conf, \%config);
127     chmod 0600, $conf;
128 }
129
130 sub dow {
131     my @dow  = qw(Mon Tue Wed Thu Fri Sat Sun);
132     my $date = shift;
133     my($y, $m, $d) = split /-/, $date;
134     return $dow[ Date_DayOfWeek($m, $d, $y) - 1 ];
135 }
136
137 sub list_events {
138     my %param;
139     $param{start} = $args{start} if $args{start};
140     $param{end}   = $args{end}   if $args{end};
141     if ($args{date}) {
142         $param{start} = $param{end} = $args{date};
143     }
144
145     my $res = call_api("events.Get", %param);
146
147     unless ($res->{eventList}->{event}) {
148         print "No event found.\n";
149         exit;
150     }
151
152     my @events = @{ $res->{eventList}->{event} };
153     for my $event (sort { $a->{start} cmp $b->{start} } @events) {
154         my($date, $time) = split / /, $event->{start};
155         printf "%7s %s (%s) %s (%s)\n",
156             $event->{id},
157             $date,
158             dow($date),
159             $event->{summary},
160             ($event->{allDayEvent} ? 'All day' : $time),
161     }
162 }
163
164 sub add_event {
165     my $summary = join ' ', @ARGV;
166     my $res = call_api("events.AddByOneBox", event => encode_utf8($summary));
167
168     print "Event ", $res->{eventList}->{event}->[0]->{id}, " created.\n";
169 }
170
171 sub delete_event {
172     for my $id (@ARGV) {
173         my $res = call_api("events.Delete", eventId => $id);
174         print "Event $id deleted.\n";
175     }
176 }
177
178 sub update_event {
179     my $id = shift @ARGV;
180     my $summary = join ' ', @ARGV;
181     # XXX this breaks other datetime fields than summary
182     my $res = call_api("events.Update", eventId => $id, summary => $summary);
183     print "Event $id updated.\n";
184 }
185
186 sub call_api {
187     my($method, %opt) = @_;
188
189     my $url = URI->new("http://30boxes.com/api/api.php");
190     $url->query_form(
191         method => $method,
192         apiKey => $config{apikey},
193         authorizedUserToken => $config{auth_token},
194         %opt,
195     );
196
197     my $res  = $ua->get($url);
198     my $data = XML::Simple::XMLin($res->content, ForceArray => [ 'event', 'tags' ], KeyAttr => undef);
199     if ($data->{stat} ne 'ok') {
200         my $msg = "call API failed: $data->{err}->{msg} ($data->{err}->{code})\n";
201         if ($data->{err}->{code} == 2 || $data->{err}->{code} == 5) {
202             $msg .= "You might need to remove $conf to authenticate again.\n";
203         }
204         die $msg;
205     }
206
207     return $data;
208 }
209
210 __END__
211
212 =head1 NAME
213
214 30boxes.pl - a command-line interface to 30boxes
215
216 =head1 SYNOPSIS
217
218   30boxes.pl [options] list
219   30boxes.pl add <summary of the event>
220   30boxes.pl del <event-id>
221
222     Options:
223       --from, --start           Start date of events to search for
224       --to, --end               End date of events to search for
225       --month                   Month of events to search for
226       --date                    Date of events to search for
227
228   30boxes.pl list
229         List all events in your calendar, starting from today to 90 days later.
230
231   30boxes.pl --from "2 weeks ago" --to today
232         List all events in your calendar, starting from 2 weeks ago to today.
233
234   30boxes.pl --month "2006 September"
235         List all events in your calendar scheduled on September, 2006.
236
237   30boxes.pl --date tomorrow
238         List all events in your calendar tomorrow.
239
240   30boxes.pl add Meeting with Bob tomorrow 3pm
241         Add new event titled "Meeting with Bob" on 3pm tomorrow.
242
243   30boxes.pl del 100
244         Deletes event with id 100.
245
246 =cut
Note: See TracBrowser for help on using the browser.