File Coverage

blib/lib/WWW/Gittip.pm
Criterion Covered Total %
statement 90 105 85.7
branch 16 22 72.7
condition 5 9 55.5
subroutine 16 17 94.1
pod 10 10 100.0
total 137 163 84.0


line stmt bran cond sub pod time code
1             package WWW::Gittip;
2 1     1   49013 use strict;
  1         2  
  1         43  
3 1     1   7 use warnings;
  1         2  
  1         33  
4              
5 1     1   1103 use LWP::UserAgent;
  1         89944  
  1         49  
6 1     1   1457 use JSON qw(from_json);
  1         36667  
  1         8  
7 1     1   1837 use HTML::TreeBuilder 5 -weak;
  1         40315  
  1         17  
8              
9             our $VERSION = '0.07';
10             my $BASE_URL = 'https://www.gratipay.com';
11              
12             =head1 NAME
13              
14             WWW::Gittip - Implementing the Gittip (now Gratipay) API. More or less.
15              
16             =head1 SYNOPSIS
17              
18             use WWW::Gittip;
19             my $gt = WWW::Gittip->new;
20             my $charts = $gt->charts;
21              
22             my $user_charts = $gt->user_charts('szabgab');
23              
24             =head1 DESCRIPTION
25              
26             This module provides a Perl interface to the L API.
27             Gittip describes itself as "a way to give small weekly cash gifts to people you
28             love and are inspired by". It is one way you can give small recurring amounts to
29             people who've written open source software that you regularly use.
30              
31             The API docs of Gittp: L
32              
33             When necessary, you can get an API key from your account on Gittip at L
34              
35             =cut
36              
37              
38             =head2 new
39              
40             my $gt = WWW::Gittip->new;
41             my $gt = WWW::Gittip->new( api_key => '123-456' );
42              
43              
44             =cut
45              
46             sub new {
47 1     1 1 9792 my ($class, %params) = @_;
48 1         7 bless \%params, $class;
49             }
50              
51             =head2 api_key
52              
53             Set/Get the API_KEY
54              
55             $gt->api_key('123-456');
56              
57             my $api_key = $gt->api_key;
58              
59             =cut
60              
61              
62             sub api_key {
63 13     13 1 27 my ($self, $value) = @_;
64 13 50       58 if (defined $value) {
65 0         0 $self->{api_key} = $value;
66             }
67 13         43 return $self->{api_key};
68             }
69              
70              
71             =head2 charts
72              
73             Returns an array reference from /about/charts.json
74             Each element in the array has the following fields:
75              
76             {
77             "active_users" => 50,
78             "charges" => 25.29,
79             "date" => "2012-06-22",
80             "total_gifts" => 62.08,
81             "total_users" => 621,
82             "weekly_gifts" => 30.08,
83             "withdrawals" => 0.00
84             },
85              
86             =cut
87              
88              
89             sub charts {
90 1     1 1 2216 my ($self) = @_;
91              
92 1         5 my $url = "$BASE_URL/about/charts.json";
93 1         6 return $self->_get($url);
94             }
95              
96             =head2 user_charts
97              
98             $gt->user_charts(USERNAME);
99              
100             Returns an array referene from /%username/charts.json
101             Each element in the array has the following fields:
102              
103             {
104             'date' => '2012-06-08',
105             'npatrons' => 0,
106             'receipts' => '0',
107             'ts_start' => '2012-06-08T12:02:45.182409+00:00'
108             }
109              
110              
111             =cut
112              
113              
114             sub user_charts {
115 2     2 1 372013 my ($self, $username) = @_;
116              
117             #croak "Invalid username '$username'" if $username eq 'about';
118              
119 2         9 my $url = "$BASE_URL/$username/charts.json";
120 2         11 return $self->_get($url);
121             }
122              
123              
124             =head2 paydays
125              
126             Returns an array reference from /about/paydays.json
127             Each element in the array has the following fields:
128              
129             {
130             'ach_fees_volume' => '0',
131             'ach_volume' => '0',
132             'charge_fees_volume' => '2.11',
133             'charge_volume' => '25.28',
134             'nachs' => 0,
135             'nactive' => 25
136             'ncc_failing' => 1,
137             'ncc_missing' => 18,
138             'ncharges' => 11,
139             'nparticipants' => 175,
140             'ntransfers' => 49,
141             'ntippers' => 12,
142             'transfer_volume' => '24.8',
143             'ts_end' => '2012-06-08T12:03:19.889215+00:00',
144             'ts_start' => '2012-06-08T12:02:45.182409+00:00',
145             },
146              
147             =cut
148              
149             sub paydays {
150 1     1 1 24742 my ($self) = @_;
151              
152 1         7 my $url = "$BASE_URL/about/paydays.json";
153 1         6 return $self->_get($url);
154             }
155              
156             =head2 stats
157              
158             Returns a reference to a hash from /about/stats.json
159             with lots of keys...
160              
161             =cut
162              
163              
164              
165             sub stats {
166 1     1 1 302802 my ($self) = @_;
167              
168 1         6 my $url = "$BASE_URL/about/stats.json";
169 1         8 return $self->_get($url);
170             }
171              
172             =head2 communities
173              
174             See L
175              
176             L
177              
178             L
179              
180             L
181              
182             Currently only returns an empty list.
183              
184             =cut
185              
186             sub communities {
187 1     1 1 6508 my ($self) = @_;
188              
189 1         5 my $url = "$BASE_URL/for/communities.json";
190 1         8 return $self->_get($url);
191             }
192              
193             =head2 user_public
194              
195             $gt->user_public(USERNAME);
196              
197             Returns an hash referene from /%username/public.json
198             Some of the fields look like these:
199              
200              
201             {
202             'id' => 25031,
203             'username' => 'szabgab',
204             'number' => 'singular',
205             'on' => 'gittip',
206             'giving' => undef,
207             'npatrons' => 7,
208             'receiving' => '5.01',
209             'goal' => undef,
210             'avatar' => 'https://avatars.githubusercontent.com/u/48833?s=128',
211             'bitcoin' => 'https://blockchain.info/address/1riba1Z6o3man18rASVyiG6NeFAhvf7rU',
212             'elsewhere' => {
213             'github' => {
214             'user_id' => '48833',
215             'id' => 85177,
216             'user_name' => 'szabgab'
217             },
218             'twitter' => {
219             'user_id' => '21182516',
220             'user_name' => 'szabgab',
221             'id' => 424525
222             }
223             },
224             };
225              
226             =cut
227              
228             sub user_public {
229 1     1 1 7871 my ($self, $username) = @_;
230              
231 1         306 my $url = "$BASE_URL/$username/public.json";
232 1         23 return $self->_get($url);
233             }
234              
235             # https://www.gratipay.com/about/tip-distribution.json
236             # returns an array of numbers \d+\.\d\d (over 8000 entries), probably the full list of tips.
237              
238             =head2 user_tips
239              
240             Requires API_KEY.
241              
242             GET /%username/tips.json and returns an array reference of hashes.
243             Each hash is looks like this
244              
245             {
246             'username' => 'perlweekly',
247             'platform' => 'gittip',
248             'amount' => '1.01'
249             }
250              
251             $gt->user_tips($username);
252              
253             =cut
254              
255             sub user_tips {
256 0     0 1 0 my ($self, $username) = @_;
257              
258 0         0 my $url = "$BASE_URL/$username/tips.json";
259 0         0 return $self->_get($url);
260             }
261              
262             =head2 community_members
263              
264             $gt->community_members('perl');
265              
266             Given the name of a community, returns a hash with 3 keys:
267             new, give, and receive corresponding to the 3 columns of the
268             https://www.gratipay.com/for/perl page.
269              
270             Each key has an array reference as the value. Each arr has several elements:
271              
272             {
273             new => [
274             {
275             name => 'szabgab',
276             },
277             {
278             name => 'rjbs',
279             },
280             ...
281             ],
282             give => [
283             ...
284             ],
285             receive => [
286             ...
287             ],
288             }
289              
290             There is no official API, so this call is scraping the HTML page.
291             Currently Gittip limits the number of people shown in each column to 100.
292              
293             The user could set the limt at a lower number using limit=... in the URL.
294             The user can also set the starting user using offset=...
295              
296             WWW::Gittip sends multiple requests as necessary to fetch all the users.
297             It uses limit=100 and the appropriate offset= for each request.
298              
299             =cut
300              
301             sub community_members {
302 1     1 1 7128 my ($self, $name) = @_;
303              
304             # limit=10
305             # offset=12
306              
307 1         9 my %NAMES = (
308             'New Members' => 'new',
309             'Top Givers' => 'give',
310             'Top Receivers' => 'receive',
311             );
312              
313 1         2 my %members;
314              
315 1         2 my $limit = 100;
316 1         3 my $offset = 0;
317 1         1 my $total;
318 1         3 while (1) {
319 6         33 my $url = "$BASE_URL/for/$name?limit=$limit&offset=$offset";
320              
321 6         1497 print "Requesting: $url\n";
322              
323 6         35 my $response = $self->_get_html($url);
324              
325 6 50       42 if (not $response->is_success) {
326 0         0 warn 'Failed';
327 0         0 return;
328             }
329              
330              
331 6         885 my $html = $response->decoded_content;
332 6         629763 my $tree = HTML::TreeBuilder->new;
333 6         2599 $tree->parse($html);
334              
335 6 100       2087998 if (not $total) {
336             #
337             #

Perl

338             #
516
339             #
members
340             #
341 1         13 my $cl = $tree->look_down('class', 'on-community');
342 1         500 my $n = $cl->look_down('class', 'number');
343 1         69 $total = $n->as_text;
344             }
345              
346 6         53 my $leaderboard = $tree->look_down('id', 'leaderboard');
347 6         5701 foreach my $ch ($leaderboard->content_list) {
348 24 100 66     182 next if not defined $ch or ref($ch) ne 'HTML::Element';
349             # The page had 4 columns, one of them was empty.
350 18         49 my $h2 = $ch->look_down('_tag', 'h2');
351 18         703 my $type = $NAMES{ $h2->as_text };
352              
353 18         375 my $group = $ch->look_down('class', 'group');
354 18         750 foreach my $member ($group->content_list) {
355 711 100 66     17073 next if not defined $member or ref($member) ne 'HTML::Element';
356             # I think these are the anonymous members.
357              
358 693         1633 my $n = $member->look_down('class', 'name');
359 693         58767 push @{ $members{$type} }, {
  693         2059  
360             name => $n->as_text,
361             };
362             }
363             }
364              
365 6         21 $offset += $limit;
366 6 50       12 if (not $total) {
367 0         0 warn "Could not find total number of members\n";
368 0         0 last;
369             }
370 6 100       8102 last if $offset >= $total;
371             }
372              
373 1         9 return \%members;
374            
375             #
376             #
377             #
378             #

New Members

379             #
380             #
381             #
  • 382             #
    383             # data-tip="">
    384             #
    385             #
    386             # style="background-image: url(\'https://avatars.githubusercontent.com/u/272648?s=128\')">
    387             #
    388             # 14 hours
    389             # dwierenga
    390             #
    391             #
    392             #
    393              
    394             }
    395              
    396             sub _get_html {
    397 13     13   38 my ($self, $url) = @_;
    398              
    399 13         158 my $ua = LWP::UserAgent->new;
    400 13         8509 $ua->timeout(10);
    401              
    402 13         246 my $api_key = $self->api_key;
    403 13 50       46 if ($api_key) {
    404 0         0 require MIME::Base64;
    405 0         0 $ua->default_header('Authorization', "Basic " . MIME::Base64::encode("$api_key:", '') );
    406             }
    407              
    408 13         67 my $response = $ua->get($url);
    409 13         19782497 return $response;
    410              
    411             }
    412              
    413              
    414             sub _get {
    415 7     7   15 my ($self, $url) = @_;
    416              
    417 7         25 my $response = $self->_get_html($url);
    418 7 100       43 if (not $response->is_success) {
    419 1         26 warn "Failed request $url\n";
    420 1         10 warn $response->status_line . "\n";
    421 1         868 return [];
    422             }
    423              
    424 6         127 my $charts = $response->decoded_content;
    425 6 50 33     2445 if (not defined $charts or $charts eq '') {
    426 0         0 warn "Empty return\n";
    427 0         0 return [];
    428             }
    429 6         10 my $data = eval { from_json $charts };
      6         40  
    430 6 50       3503 if ($@) {
    431 0         0 warn $@;
    432 0         0 warn "Data received: '$charts'\n";
    433 0         0 $data = [];
    434             }
    435 6         292 return $data;
    436             }
    437              
    438              
    439              
    440             =head1 AUTHOR
    441              
    442             Gabor Szabo L
    443              
    444             =head1 LICENSE
    445              
    446             Copyright (c) 2014, Gabor Szabo L
    447              
    448             This library is free software; you can redistribute it and/or modify
    449             it under the same terms as Perl itself.
    450              
    451             =cut
    452              
    453             1;
    454              
    455