File Coverage

blib/lib/App/TracksBot.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package App::TracksBot;
2              
3 1     1   21541 use strict;
  1         2  
  1         31  
4 1     1   4 use warnings;
  1         2  
  1         55  
5 1     1   6 use feature 'say';
  1         5  
  1         83  
6 1     1   4 use feature 'switch';
  1         2  
  1         37  
7              
8 1     1   455 use AnyEvent::WebService::Tracks;
  0            
  0            
9             use AnyEvent::XMPP::IM::Connection;
10             use AnyEvent::XMPP::Util qw(bare_jid);
11             use List::MoreUtils qw(first_value);
12             use YAML qw(LoadFile);
13              
14             our $VERSION = '0.01';
15              
16             my $tracks;
17             my $default_context;
18             my %whitelist;
19              
20             sub setup_xmpp {
21             my ( $config ) = @_;
22              
23             $config = $config->{'xmpp'};
24             my %params;
25              
26             if(delete $config->{'google_talk'}) {
27             $params{'domain'} = 'gmail.com';
28             $params{'host'} = 'talk.google.com';
29             $params{'old_style_ssl'} = 1;
30             $params{'port'} = 5223;
31             }
32             @params{keys %$config} = values %$config;
33              
34             return AnyEvent::XMPP::IM::Connection->new(%params);
35             }
36              
37             sub setup_tracks {
38             my ( $config ) = @_;
39              
40             $config = $config->{'tracks'};
41              
42             return AnyEvent::WebService::Tracks->new(%$config);
43             }
44              
45             sub handle_error {
46             my ( undef, $error ) = @_;
47              
48             say $error->string;
49             }
50              
51             sub send_reply {
52             my ( $msg, $body ) = @_;
53              
54             my $reply = $msg->make_reply;
55             $reply->type('chat');
56             $reply->add_body($body);
57             $reply->send;
58             }
59              
60             sub get_help {
61             return <
62              
63             add - Create a new todo item.
64             create context - Create a new context.
65             create project - Create a new project.
66             create todo - Create a new todo item.
67             contexts - List available contexts.
68             help - Display this help.
69             projects - List available projects.
70             todos - List available todos.
71             todos in context - Lists todos in the given context.
72             todos in project - Lists todos in the given project.
73             HELP
74             }
75              
76             sub create_todo {
77             my ( $msg, $tracks, $description ) = @_;
78              
79             $tracks->create_todo($description, $default_context, sub {
80             my ( $todo, $error ) = @_;
81              
82             if($todo) {
83             send_reply($msg, "Created a new todo '$description' as todo #" . $todo->id);
84             } else {
85             send_reply($msg, "An error occurred when creating a new todo: $error");
86             }
87             });
88             }
89              
90             sub create_context {
91             my ( $msg, $tracks, $name ) = @_;
92              
93             $tracks->create_context($name, sub {
94             my ( $context, $error ) = @_;
95              
96             if($context) {
97             send_reply($msg, "Created a new context '$name' as context #" . $context->id);
98             } else {
99             send_reply($msg, "An error occurred when creating a new context: $error");
100             }
101             });
102             }
103              
104             sub create_project {
105             my ( $msg, $tracks, $name ) = @_;
106              
107             $tracks->create_project($name, sub {
108             my ( $project, $error ) = @_;
109              
110             if($project) {
111             send_reply($msg, "Created a new project '$name' as project #" . $project->id);
112             } else {
113             send_reply($msg, "An error occurred when creating a new project $error");
114             }
115             });
116             }
117              
118             sub show_contexts {
119             my ( $msg, $tracks ) = @_;
120              
121             $tracks->contexts(sub {
122             my ( $contexts, $error ) = @_;
123              
124             if($contexts) {
125             if(@$contexts) {
126             send_reply($msg, join('', map { "\n" . $_->name } @$contexts));
127             } else {
128             send_reply($msg, 'No contexts');
129             }
130             } else {
131             send_reply($msg, "An error occurred when fetching the list of contexts: $error");
132             }
133             });
134             }
135              
136             sub show_projects {
137             my ( $msg, $tracks ) = @_;
138              
139             $tracks->projects(sub {
140             my ( $projects, $error ) = @_;
141              
142             if($projects) {
143             if(@$projects) {
144             send_reply($msg, join('', map { "\n" . $_->name } @$projects));
145             } else {
146             send_reply($msg, 'No projects');
147             }
148             } else {
149             send_reply($msg, "An error occurred when fetching the list of projects: $error");
150             }
151             });
152             }
153              
154             sub show_todos {
155             my ( $msg, $tracks ) = @_;
156              
157             $tracks->todos(sub {
158             my ( $todos, $error ) = @_;
159              
160             if($todos) {
161             if(@$todos) {
162             send_reply($msg, join('', map { "\n" . $_->description } @$todos));
163             } else {
164             send_reply($msg, 'No todos');
165             }
166             } else {
167             send_reply($msg, "An error occurred when fetching the list of todos $error");
168             }
169             });
170             }
171              
172             sub show_todos_in_context {
173             my ( $msg, $tracks, $name ) = @_;
174              
175             $tracks->contexts(sub {
176             my ( $contexts, $error ) = @_;
177              
178             if($contexts) {
179             my $context = first_value { $_->name eq $name } @$contexts;
180              
181             if($context) {
182             $context->todos(sub {
183             my ( $todos, $error ) = @_;
184              
185             if($todos) {
186             if(@$todos) {
187             send_reply($msg, join('', map { "\n" . $_->description } @$todos));
188             } else {
189             send_reply($msg, "No todos in context '$name'");
190             }
191             } else {
192             send_reply($msg, "An error occurred when fetching the list of todos: $error");
193             }
194             });
195             } else {
196             send_reply($msg, "There is no context named '$name'");
197             }
198             } else {
199             send_reply($msg, "An error occurred when fetching the list of contexts: $error");
200             }
201             });
202             }
203              
204             sub show_todos_in_project {
205             my ( $msg, $tracks, $name ) = @_;
206              
207             $tracks->projects(sub {
208             my ( $projects, $error ) = @_;
209              
210             if($projects) {
211             my $project = first_value { $_->name eq $name } @$projects;
212              
213             if($project) {
214             $project->todos(sub {
215             my ( $todos, $error ) = @_;
216              
217             if($todos) {
218             if(@$todos) {
219             send_reply($msg, join('', map { "\n" . $_->description } @$todos));
220             } else {
221             send_reply($msg, "No todos in project '$name'");
222             }
223             } else {
224             send_reply($msg, "An error occurred when fetching the list of todos: $error");
225             }
226             });
227             } else {
228             send_reply($msg, "There is no project named '$name'");
229             }
230             } else {
231             send_reply($msg, "An error occurred when fetching the list of projects: $error");
232             }
233             });
234             }
235              
236             sub dispatch_body {
237             my ( $msg, $body ) = @_;
238              
239             given($body) {
240             when(/^add\s+(?.*)\s*$/) {
241             create_todo($msg, $tracks, $+{'description'});
242             }
243             when(/^create\s+context\s+(?.*)\s*$/) {
244             create_context($msg, $tracks, $+{'name'});
245             }
246             when(/^create\s+project\s+(?.*)\s*$/) {
247             create_project($msg, $tracks, $+{'name'});
248             }
249             when(/^create\s+todo\s+(?.*)\s*$/) {
250             create_todo($msg, $tracks, $+{'description'});
251             }
252             when('contexts') {
253             show_contexts($msg, $tracks);
254             }
255             when('help') {
256             send_reply($msg, get_help);
257             }
258             when('projects') {
259             show_projects($msg, $tracks);
260             }
261             when('todos') {
262             show_todos($msg, $tracks);
263             }
264             when(/^todos\s+in\s+context\s+(?.*)\s*$/) {
265             show_todos_in_context($msg, $tracks, $+{'name'});
266             }
267             when(/^todos\s+in\s+project\s+(?.*)\s*$/) {
268             show_todos_in_project($msg, $tracks, $+{'name'});
269             }
270             default {
271             send_reply($msg, "I don't understand; try 'help'");
272             }
273             }
274             }
275              
276             sub handle_message {
277             my ( undef, $msg ) = @_;
278              
279             my $from = bare_jid($msg->from);
280             my $body = $msg->body;
281              
282             return unless $body;
283             unless(exists $whitelist{$from}) {
284             send_reply($msg, "I'm not supposed to talk to strangers!");
285             return;
286             }
287              
288             dispatch_body($msg, $body);
289             }
290              
291             sub run {
292             shift;
293             die "usage: $0 [config file]\n" unless @_;
294              
295             my ( $config ) = @_;
296             $config = LoadFile($config);
297             my $conn = setup_xmpp($config);
298             %whitelist = map { $_ => 1 } @{ $config->{'whitelist'} };
299             $tracks = setup_tracks($config);
300             my $cond = AnyEvent->condvar;
301              
302             $conn->reg_cb(
303             error => \&handle_error,
304             message => \&handle_message,
305             );
306              
307             $tracks->contexts(sub {
308             my ( $contexts, $error ) = @_;
309              
310             my $name = $config->{'tracks'}{'default_context'};
311             die "No default context specified\n" unless defined $name;
312              
313             if($contexts) {
314             $default_context = first_value { $_->name eq $name } @$contexts;
315             die "Default context '$name' not found\n" unless $default_context;
316             $conn->connect;
317             } else {
318             die "An error occurred: $error\n";
319             }
320             });
321              
322             $cond->recv;
323             }
324              
325             1;
326              
327             __END__