Coverage Summary

# File Coverage

lib/Protocol/Notifo.pm
Criterion | Covered | Total | %
--- | --- | --- | ---
statement | 59 | 59 | 100.0
branch | 14 | 16 | 87.5
condition | 2 | 3 | 66.7
subroutine | 13 | 13 | 100.0
pod | 0 | 4 | 0.0
total | 88 | 95 | 92.6

line | stmt | bran | cond | sub | pod | time | code
--- | --- | --- | --- | --- | --- | --- | ---
1 |  |  |  |  |  |  | package Protocol::Notifo;
2 |  |  |  |  |  |  |
3 |  |  |  |  |  |  | # ABSTRACT: utilities to build requests for the notifo.com service
4 |  |  |  |  |  |  |
5 | 3 |  |  | 3 |  | 15 | use strict;
| 3 |  |  |  |  | 6 |
| 3 |  |  |  |  | 68 |
6 | 3 |  |  | 3 |  | 10 | use warnings;
| 3 |  |  |  |  | 4 |
| 3 |  |  |  |  | 95 |
7 | 3 |  |  | 3 |  | 11 | use Carp 'confess';
| 3 |  |  |  |  | 3 |
| 3 |  |  |  |  | 128 |
8 | 3 |  |  | 3 |  | 716 | use JSON 'decode_json';
| 3 |  |  |  |  | 32849 |
| 3 |  |  |  |  | 26 |
9 | 3 |  |  | 3 |  | 1428 | use MIME::Base64 'encode_base64';
| 3 |  |  |  |  | 1846 |
| 3 |  |  |  |  | 192 |
10 | 3 |  |  | 3 |  | 738 | use File::HomeDir;
| 3 |  |  |  |  | 37832 |
| 3 |  |  |  |  | 211 |
11 | 3 |  |  | 3 |  | 749 | use File::Spec::Functions qw( catfile );
| 3 |  |  |  |  | 1762 |
| 3 |  |  |  |  | 200 |
12 | 3 |  |  | 3 |  | 736 | use namespace::clean;
| 3 |  |  |  |  | 55646 |
| 3 |  |  |  |  | 30 |
13 |  |  |  |  |  |  |
14 |  |  |  |  |  |  | =constructor new
15 |  |  |  |  |  |  |
16 |  |  |  |  |  |  | Creates new C<Protocol::Notifo> object.
17 |  |  |  |  |  |  |
18 |  |  |  |  |  |  | It first tries to load default values from a configuration file. If you
19 |  |  |  |  |  |  | set the environment variable C<NOTIFO_CFG>, it will try that. If not, it
20 |  |  |  |  |  |  | will default to L<File::HomeDir/my_home|File::HomeDir->my_home()>. See
21 |  |  |  |  |  |  | L<CONFIGURATION FILE> for the format of the file.
22 |  |  |  |  |  |  |
23 |  |  |  |  |  |  | You can also pass a hash of options, that will override the
24 |  |  |  |  |  |  | configuration file. The following options are accepted:
25 |  |  |  |  |  |  |
26 |  |  |  |  |  |  | =over 4
27 |  |  |  |  |  |  |
28 |  |  |  |  |  |  | =item user
29 |  |  |  |  |  |  |
30 |  |  |  |  |  |  | The API username.
31 |  |  |  |  |  |  |
32 |  |  |  |  |  |  | =item api_key
33 |  |  |  |  |  |  |
34 |  |  |  |  |  |  | The API key.
35 |  |  |  |  |  |  |
36 |  |  |  |  |  |  | =back
37 |  |  |  |  |  |  |
38 |  |  |  |  |  |  | Values for this two options can be found in the
39 |  |  |  |  |  |  | L<http://notifo.com/user/settings|user settings page> of
40 |  |  |  |  |  |  | L<http://notifo.com/|Notifo site>.
41 |  |  |  |  |  |  |
42 |  |  |  |  |  |  | =cut
43 |  |  |  |  |  |  | sub new {
44 | 7 |  |  | 7 | 0 | 25 | my ($class, %args) = @_;
45 | 7 |  |  |  |  | 17 | my $self = bless $class->_read_config_file, $class;
46 |  |  |  |  |  |  |
47 | 7 |  |  |  |  | 25 | for my $f (qw( user api_key )) {
48 | 13 | 100 |  |  |  | 37 | $self->{$f} = $args{$f} if exists $args{$f};
49 | 13 | 100 |  |  |  | 55 | confess("Missing required parameter '$f' to new(), ") unless $self->{$f};
50 |  |  |  |  |  |  | }
51 |  |  |  |  |  |  |
52 | 5 |  |  |  |  | 12 | $self->{base_url} = 'https://api.notifo.com/v1';
53 | 5 |  |  |  |  | 51 | $self->{auth_hdr} = encode_base64(join(':', @$self{qw(user api_key)}), '');
54 |  |  |  |  |  |  |
55 | 5 |  |  |  |  | 55 | return $self;
56 |  |  |  |  |  |  | }
57 |  |  |  |  |  |  |
58 |  |  |  |  |  |  |
59 |  |  |  |  |  |  | =method parse_response
60 |  |  |  |  |  |  |
61 |  |  |  |  |  |  | Accepts two parameters, a HTTP response code and the response content.
62 |  |  |  |  |  |  | It parses the content, adds the HTTP response code and returns a hashref
63 |  |  |  |  |  |  | with all the fields.
64 |  |  |  |  |  |  |
65 |  |  |  |  |  |  | The following fields are present on all responses:
66 |  |  |  |  |  |  |
67 |  |  |  |  |  |  | =over 4
68 |  |  |  |  |  |  |
69 |  |  |  |  |  |  | =item status
70 |  |  |  |  |  |  |
71 |  |  |  |  |  |  | A string, either C<success> or C<error>.
72 |  |  |  |  |  |  |
73 |  |  |  |  |  |  | =item http_code
74 |  |  |  |  |  |  |
75 |  |  |  |  |  |  | The HTTP code of the response.
76 |  |  |  |  |  |  |
77 |  |  |  |  |  |  | =item response_code
78 |  |  |  |  |  |  |
79 |  |  |  |  |  |  | A Notifo.com integer response code.
80 |  |  |  |  |  |  |
81 |  |  |  |  |  |  | =item response_message
82 |  |  |  |  |  |  |
83 |  |  |  |  |  |  | A text description of the response. Specially useful with
84 |  |  |  |  |  |  | C<status> C<error>.
85 |  |  |  |  |  |  |
86 |  |  |  |  |  |  | =back
87 |  |  |  |  |  |  |
88 |  |  |  |  |  |  | =cut
89 |  |  |  |  |  |  | sub parse_response {
90 | 3 |  |  | 3 | 0 | 7 | my ($self, $http_code, $content) = @_;
91 |  |  |  |  |  |  |
92 | 3 |  |  |  |  | 56 | my $res = decode_json($content);
93 | 3 |  |  |  |  | 5 | $res->{http_code} = $http_code;
94 |  |  |  |  |  |  |
95 | 3 |  |  |  |  | 28 | return $res;
96 |  |  |  |  |  |  | }
97 |  |  |  |  |  |  |
98 |  |  |  |  |  |  |
99 |  |  |  |  |  |  | =method send_notification
100 |  |  |  |  |  |  |
101 |  |  |  |  |  |  | Prepares a request for the C<send_notification> API.
102 |  |  |  |  |  |  |
103 |  |  |  |  |  |  | Accepts a hash with options. The following options are supported:
104 |  |  |  |  |  |  |
105 |  |  |  |  |  |  | =over 4
106 |  |  |  |  |  |  |
107 |  |  |  |  |  |  | =item msg
108 |  |  |  |  |  |  |
109 |  |  |  |  |  |  | The notification message. This parameter is B<required>.
110 |  |  |  |  |  |  |
111 |  |  |  |  |  |  | =item to
112 |  |  |  |  |  |  |
113 |  |  |  |  |  |  | The destination user. If the API username/key pair used is of a User
114 |  |  |  |  |  |  | account, then this parameter is ignored and can be safelly ommited.
115 |  |  |  |  |  |  |
116 |  |  |  |  |  |  | A User account can only send notifications to itself. A Service account
117 |  |  |  |  |  |  | can send notifications to all his subscribed users.
118 |  |  |  |  |  |  |
119 |  |  |  |  |  |  | =item label
120 |  |  |  |  |  |  |
121 |  |  |  |  |  |  | A label describing the application that is sending the
122 |  |  |  |  |  |  | notification. With Service accounts, this option is ignored and the
123 |  |  |  |  |  |  | Service Name is used.
124 |  |  |  |  |  |  |
125 |  |  |  |  |  |  | =item title
126 |  |  |  |  |  |  |
127 |  |  |  |  |  |  | The title or subject of the notification.
128 |  |  |  |  |  |  |
129 |  |  |  |  |  |  | =item uri
130 |  |  |  |  |  |  |
131 |  |  |  |  |  |  | The URL for the event. On some clients you can click the notification and jump to this URL.
132 |  |  |  |  |  |  |
133 |  |  |  |  |  |  | =back
134 |  |  |  |  |  |  |
135 |  |  |  |  |  |  | The return value is a hashref with all the relevant information to
136 |  |  |  |  |  |  | perform the HTTP request: the url and the method to use, the
137 |  |  |  |  |  |  | Authorization header, and the query form fields.
138 |  |  |  |  |  |  |
139 |  |  |  |  |  |  | An example:
140 |  |  |  |  |  |  |
141 |  |  |  |  |  |  | url => "https://api.notifo.com/v1/send_notification",
142 |  |  |  |  |  |  | method => "POST",
143 |  |  |  |  |  |  | args => {
144 |  |  |  |  |  |  | label => "my application",
145 |  |  |  |  |  |  | msg => "hello there!",
146 |  |  |  |  |  |  | title => "welcome",
147 |  |  |  |  |  |  | to => "user_x",
148 |  |  |  |  |  |  | uri => "http://www.example.com/welcome/"
149 |  |  |  |  |  |  | },
150 |  |  |  |  |  |  | headers => {
151 |  |  |  |  |  |  | Authorization => "bWU6bXlfa2V5"
152 |  |  |  |  |  |  | },
153 |  |  |  |  |  |  |
154 |  |  |  |  |  |  | The following keys are always present in the hashref:
155 |  |  |  |  |  |  |
156 |  |  |  |  |  |  | =over 4
157 |  |  |  |  |  |  |
158 |  |  |  |  |  |  | =item url
159 |  |  |  |  |  |  |
160 |  |  |  |  |  |  | The URL where the HTTP request should be sent to.
161 |  |  |  |  |  |  |
162 |  |  |  |  |  |  | =item method
163 |  |  |  |  |  |  |
164 |  |  |  |  |  |  | The HTTP method to use.
165 |  |  |  |  |  |  |
166 |  |  |  |  |  |  | =item args
167 |  |  |  |  |  |  |
168 |  |  |  |  |  |  | A hashref with all the URL query form fields and values.
169 |  |  |  |  |  |  |
170 |  |  |  |  |  |  | =item headers
171 |  |  |  |  |  |  |
172 |  |  |  |  |  |  | A hashref with all the headers to include in the HTTP request.
173 |  |  |  |  |  |  |
174 |  |  |  |  |  |  | =back
175 |  |  |  |  |  |  |
176 |  |  |  |  |  |  | =cut
177 |  |  |  |  |  |  | sub send_notification {
178 | 7 |  |  | 7 | 0 | 27 | my ($self, %args) = @_;
179 |  |  |  |  |  |  |
180 | 7 |  |  |  |  | 55 | my %call = (
181 |  |  |  |  |  |  | url => "$self->{base_url}/send_notification",
182 |  |  |  |  |  |  | method => 'POST',
183 |  |  |  |  |  |  | headers => {Authorization => $self->{auth_hdr}},
184 |  |  |  |  |  |  | args => {},
185 |  |  |  |  |  |  | );
186 |  |  |  |  |  |  |
187 | 7 |  |  |  |  | 25 | for my $f (qw( to msg label title uri )) {
188 | 35 |  |  |  |  | 37 | my $v = $args{$f};
189 | 35 | 100 |  |  |  | 52 | next unless defined $v;
190 |  |  |  |  |  |  |
191 | 16 |  |  |  |  | 28 | $call{args}{$f} = $v;
192 |  |  |  |  |  |  | }
193 |  |  |  |  |  |  |
194 | 7 | 100 |  |  |  | 25 | confess("Missing required argument 'msg', ") unless $call{args}{msg};
195 |  |  |  |  |  |  |
196 | 6 |  |  |  |  | 62 | return \%call;
197 |  |  |  |  |  |  | }
198 |  |  |  |  |  |  |
199 |  |  |  |  |  |  | =method config_file
200 |  |  |  |  |  |  |
201 |  |  |  |  |  |  | Returns the configuration file that this module will attempt to use.
202 |  |  |  |  |  |  |
203 |  |  |  |  |  |  | =cut
204 |  |  |  |  |  |  | sub config_file {
205 | 10 |  |  | 10 | 0 | 16 | my ($self) = @_;
206 |  |  |  |  |  |  |
207 | 10 |  | 66 |  |  | 115 | return $ENV{NOTIFO_CFG} || catfile(File::HomeDir->my_home, '.notifo.rc');
208 |  |  |  |  |  |  | }
209 |  |  |  |  |  |  |
210 |  |  |  |  |  |  |
211 |  |  |  |  |  |  | =head1 CONFIGURATION FILE
212 |  |  |  |  |  |  |
213 |  |  |  |  |  |  | The configuration file is line based. Empty lines os just spaces/tabs,
214 |  |  |  |  |  |  | or lines starting with # are ignored.
215 |  |  |  |  |  |  |
216 |  |  |  |  |  |  | All other lines are parsed for commands, in the form
217 |  |  |  |  |  |  | C<command separator value>. The C<separator> can be a C<=> or a C<:>.
218 |  |  |  |  |  |  |
219 |  |  |  |  |  |  | See the L<CONSTRUCTORS|new() constructor> for the commands you can use,
220 |  |  |  |  |  |  | they are the same ones as the accepted options.
221 |  |  |  |  |  |  |
222 |  |  |  |  |  |  | =cut
223 |  |  |  |  |  |  | sub _read_config_file {
224 | 7 |  |  | 7 |  | 13 | my ($self) = @_;
225 | 7 |  |  |  |  | 9 | my %opts;
226 |  |  |  |  |  |  |
227 | 7 |  |  |  |  | 16 | my $fn = $self->config_file;
228 | 7 | 100 |  |  |  | 472 | return \%opts unless -r $fn;
229 |  |  |  |  |  |  |
230 | 2 | 50 |  |  |  | 99 | open(my $fh, '<', $fn) || confess("Could not open file '$fn': $!, ");
231 |  |  |  |  |  |  |
232 | 2 |  |  |  |  | 57 | while (my $l = <$fh>) {
233 | 16 |  |  |  |  | 18 | chomp($l);
234 | 16 |  |  |  |  | 113 | $l =~ s/^\s*(#.*)?|\s*$//g;
235 | 16 | 100 |  |  |  | 44 | next unless $l;
236 |  |  |  |  |  |  |
237 | 6 |  |  |  |  | 29 | my ($k, $v) = $l =~ m/(\S+)\s*[=:]\s*(.*)/;
238 | 6 | 50 |  |  |  | 15 | confess("Could not parse line $. of $fn ('$l'), ") unless $k;
239 |  |  |  |  |  |  |
240 | 6 |  |  |  |  | 31 | $opts{$k} = $v;
241 |  |  |  |  |  |  | }
242 |  |  |  |  |  |  |
243 | 2 |  |  |  |  | 59 | return \%opts;
244 |  |  |  |  |  |  | }
245 |  |  |  |  |  |  |
246 |  |  |  |  |  |  | 1;
247 |  |  |  |  |  |  |
248 |  |  |  |  |  |  | __END__
249 |  |  |  |  |  |  |
250 |  |  |  |  |  |  | =head1 SYNOPSIS
251 |  |  |  |  |  |  |
252 |  |  |  |  |  |  | ## Reads user and api_key from configuration file
253 |  |  |  |  |  |  | my $pn = Protocol::Notifo->new;
254 |  |  |  |  |  |  |
255 |  |  |  |  |  |  | ## Use a particular user and api_key, overrides configuration file
256 |  |  |  |  |  |  | my $pn = Protocol::Notifo->new(user => 'me', api_key => 'my_key');
257 |  |  |  |  |  |  |
258 |  |  |  |  |  |  | my $req = $pn->send_notification(msg => 'Hi!');
259 |  |  |  |  |  |  |
260 |  |  |  |  |  |  | .... send $req, get a response back ....
261 |  |  |  |  |  |  |
262 |  |  |  |  |  |  | my $res = $pn->parse_response($response_http_code, $response_body);
263 |  |  |  |  |  |  |
264 |  |  |  |  |  |  | .... do stuff with $res ....
265 |  |  |  |  |  |  |
266 |  |  |  |  |  |  |
267 |  |  |  |  |  |  | =head1 DESCRIPTION
268 |  |  |  |  |  |  |
269 |  |  |  |  |  |  | This module provides a API to prepare requests to the
270 |  |  |  |  |  |  | L<http://api.notifo.com/|notifo.com API>.
271 |  |  |  |  |  |  |
272 |  |  |  |  |  |  | The module doesn't actually execute a HTTP request. It only prepares
273 |  |  |  |  |  |  | all the information required for such request to be performed. As such
274 |  |  |  |  |  |  | this module is not to be used by end users, but by writters of the
275 |  |  |  |  |  |  | Notifo.com API.
276 |  |  |  |  |  |  |
277 |  |  |  |  |  |  | If you are an end-user and want to call the API, you should look into
278 |  |  |  |  |  |  | the modules L<WebService::Notifo> and L<AnyEvent::WebService::Notifo>.
279 |  |  |  |  |  |  |
280 |  |  |  |  |  |  | This module supports both the User API and the Service API.
281 |  |  |  |  |  |  | Differences between the behaviour of the two are noted in this
282 |  |  |  |  |  |  | documentation where relevant.
283 |  |  |  |  |  |  |
284 |  |  |  |  |  |  | You need a Notifo.com account to be able to use this module. The account
285 |  |  |  |  |  |  | will give you access to a API username, and a API key. Both are required
286 |  |  |  |  |  |  | arguments of our L</CONSTRUCTORS|constructors>.
287 |  |  |  |  |  |  |
288 |  |  |  |  |  |  | The module also supports a configuration file. See
289 |  |  |  |  |  |  | L</config_file|config_file()> to learn which configuration files are
290 |  |  |  |  |  |  | loaded automatically, if found.
291 |  |  |  |  |  |  |
292 |  |  |  |  |  |  | For all the details of this API, check out the site
293 |  |  |  |  |  |  | L<http://api.notifo.com/|http://api.notifo.com/>.
294 |  |  |  |  |  |  |
295 |  |  |  |  |  |  |
296 |  |  |  |  |  |  | =head1 TODO
297 |  |  |  |  |  |  |
298 |  |  |  |  |  |  | Future versions of this module will implement the other APIs:
299 |  |  |  |  |  |  |
300 |  |  |  |  |  |  | =over 4
301 |  |  |  |  |  |  |
302 |  |  |  |  |  |  | =item subscribe_user
303 |  |  |  |  |  |  |
304 |  |  |  |  |  |  | =item send_message
305 |  |  |  |  |  |  |
306 |  |  |  |  |  |  | =back
307 |  |  |  |  |  |  |
308 |  |  |  |  |  |  | Patches welcome.
309 |  |  |  |  |  |  |
310 |  |  |  |  |  |  |
311 |  |  |  |  |  |  | =cut
312 |  |  |  |  |  |  |