line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package WebService::HabitRPG; |
2
|
3
|
|
|
3
|
|
51435
|
use v5.010; |
|
3
|
|
|
|
|
11
|
|
3
|
3
|
|
|
3
|
|
16
|
use strict; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
74
|
|
4
|
3
|
|
|
3
|
|
16
|
use warnings; |
|
3
|
|
|
|
|
13
|
|
|
3
|
|
|
|
|
94
|
|
5
|
3
|
|
|
3
|
|
1554
|
use autodie; |
|
3
|
|
|
|
|
36313
|
|
|
3
|
|
|
|
|
19
|
|
6
|
3
|
|
|
3
|
|
21220
|
use Moo; |
|
3
|
|
|
|
|
45606
|
|
|
3
|
|
|
|
|
19
|
|
7
|
3
|
|
|
3
|
|
8948
|
use WWW::Mechanize; |
|
3
|
|
|
|
|
566405
|
|
|
3
|
|
|
|
|
162
|
|
8
|
3
|
|
|
3
|
|
3379
|
use Method::Signatures 20121201; |
|
3
|
|
|
|
|
237619
|
|
|
3
|
|
|
|
|
22
|
|
9
|
3
|
|
|
3
|
|
3218
|
use WebService::HabitRPG::Task; |
|
3
|
|
|
|
|
11
|
|
|
3
|
|
|
|
|
92
|
|
10
|
3
|
|
|
3
|
|
2659
|
use JSON::Any; |
|
3
|
|
|
|
|
11113
|
|
|
3
|
|
|
|
|
13
|
|
11
|
3
|
|
|
3
|
|
14144
|
use Data::Dumper; |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
196
|
|
12
|
3
|
|
|
3
|
|
17
|
use Carp qw(croak); |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
1058
|
|
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
our $DEBUG = $ENV{HRPG_DEBUG} || 0; |
15
|
|
|
|
|
|
|
our $TAG_PREFIX_CHARACTER = '^'; |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
# ABSTRACT: Perl interface to the HabitRPG API |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
our $VERSION = '0.23'; # VERSION: Generated by DZP::OurPkg:Version |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
has 'api_token' => (is => 'ro'); # aka x-api-key |
23
|
|
|
|
|
|
|
has 'user_id' => (is => 'ro'); # aka x-api-user |
24
|
|
|
|
|
|
|
has 'agent' => (is => 'rw'); |
25
|
|
|
|
|
|
|
has 'api_base' => (is => 'ro', default => sub { 'https://habitrpg.com/api/v1' }); |
26
|
|
|
|
|
|
|
has '_last_json' => (is => 'rw'); # For debugging |
27
|
|
|
|
|
|
|
has 'tags' => (is => 'rw'); |
28
|
|
|
|
|
|
|
has 'tag_prefix' => (is => 'rw', default => sub { '^' }); |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
# use constant URL_BASE => 'https://habitrpg.com/api/v1'; |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
sub BUILD { |
33
|
3
|
|
|
3
|
0
|
31
|
my ($self, $args) = @_; |
34
|
|
|
|
|
|
|
|
35
|
3
|
|
100
|
|
|
18
|
my $keep_alive = $args->{keep_alive} // 1; |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
# Set a default agent if we don't already have one. |
38
|
|
|
|
|
|
|
|
39
|
3
|
50
|
|
|
|
22
|
if (not $self->agent) { |
40
|
3
|
|
|
|
|
50
|
$self->agent( |
41
|
|
|
|
|
|
|
WWW::Mechanize->new( |
42
|
|
|
|
|
|
|
agent => "Perl/$], WebService::HabitRPG/" . $self->VERSION, |
43
|
|
|
|
|
|
|
keep_alive => $keep_alive, |
44
|
|
|
|
|
|
|
) |
45
|
|
|
|
|
|
|
); |
46
|
|
|
|
|
|
|
} |
47
|
|
|
|
|
|
|
|
48
|
3
|
|
|
|
|
46806
|
return; |
49
|
|
|
|
|
|
|
} |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
|
52
|
3
|
0
|
|
3
|
|
3213
|
method user() { return $self->_get_request( '/user' ); } |
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
|
55
|
3
|
0
|
|
3
|
|
440955
|
method tasks($type where qr{^(?: habit | daily | todo | reward | )$}x = "") { |
|
3
|
0
|
|
3
|
|
3368
|
|
|
3
|
0
|
|
0
|
|
74
|
|
|
3
|
|
|
|
|
21
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
56
|
0
|
0
|
|
|
|
|
if ($type) { |
57
|
0
|
|
|
|
|
|
return $self->_get_tasks( "/user/tasks?type=$type" ); |
58
|
|
|
|
|
|
|
} |
59
|
0
|
|
|
|
|
|
return $self->_get_tasks( "/user/tasks" ); |
60
|
|
|
|
|
|
|
} |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
|
63
|
3
|
0
|
|
3
|
|
7626
|
method get_task($task_id) { |
|
0
|
0
|
|
0
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
# _get_tasks() always returns an array ref, so we unpack that here. |
66
|
|
|
|
|
|
|
|
67
|
0
|
|
|
|
|
|
return $self->_get_tasks("/user/task/$task_id")->[0]; |
68
|
|
|
|
|
|
|
} |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
|
71
|
3
|
|
|
3
|
|
74626
|
method new_task( |
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
72
|
3
|
0
|
|
3
|
|
439
|
:$type! where qr{^(?: habit | daily | todo | reward )$}x, |
|
3
|
0
|
|
|
|
9
|
|
|
3
|
|
|
|
|
18
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
73
|
0
|
0
|
|
|
|
|
:$text!, |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
74
|
0
|
|
|
|
|
|
:$completed, |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
75
|
0
|
0
|
|
|
|
|
:$value = 0, |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
76
|
0
|
0
|
|
|
|
|
:$note = '', |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
77
|
0
|
0
|
|
|
|
|
:$up = 1, |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
78
|
0
|
0
|
|
|
|
|
:$down = 1, |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
79
|
0
|
0
|
|
|
|
|
:$extend = {}, |
|
0
|
0
|
|
|
|
|
|
|
0
|
0
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
) { |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
# Magical boolification for JSONification. |
83
|
|
|
|
|
|
|
# TODO: These work with JSON::XS. Do they work with other backends? |
84
|
|
|
|
|
|
|
|
85
|
0
|
0
|
|
|
|
|
$up = $up ? \1 : \0; |
86
|
0
|
0
|
|
|
|
|
$down = $down ? \1 : \0; |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
# TODO : The API spec doesn't allow the submission of up/down |
89
|
|
|
|
|
|
|
# values, but I feel that *should* be allowed, otherwise |
90
|
|
|
|
|
|
|
# creating goals isn't full-featured. |
91
|
|
|
|
|
|
|
|
92
|
0
|
|
|
|
|
|
my $payload = $self->_encode_json({ |
93
|
|
|
|
|
|
|
type => $type, |
94
|
|
|
|
|
|
|
text => $text, |
95
|
|
|
|
|
|
|
completed => $completed, |
96
|
|
|
|
|
|
|
value => $value, |
97
|
|
|
|
|
|
|
note => $note, |
98
|
|
|
|
|
|
|
up => $up, |
99
|
|
|
|
|
|
|
down => $down, |
100
|
|
|
|
|
|
|
%$extend, |
101
|
|
|
|
|
|
|
}); |
102
|
|
|
|
|
|
|
|
103
|
0
|
|
|
|
|
|
my $req = $self->_build_request('POST', '/user/task'); |
104
|
|
|
|
|
|
|
|
105
|
0
|
|
|
|
|
|
$req->content( $payload ); |
106
|
|
|
|
|
|
|
|
107
|
0
|
|
|
|
|
|
return $self->_request( $req ); |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
} |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
|
112
|
3
|
|
|
3
|
|
21497
|
method updown( |
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
|
|
|
|
113
|
0
|
0
|
|
|
|
|
$task!, |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
114
|
3
|
0
|
|
3
|
|
424
|
$direction! where qr{up|down} |
|
3
|
0
|
|
|
|
7
|
|
|
3
|
0
|
|
|
|
17
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
) { |
116
|
|
|
|
|
|
|
|
117
|
0
|
|
|
|
|
|
my $req = $self->_build_request('POST', "/user/tasks/$task/$direction"); |
118
|
|
|
|
|
|
|
|
119
|
0
|
|
|
|
|
|
return $self->_request( $req ); |
120
|
|
|
|
|
|
|
} |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
# Convenience methods |
124
|
3
|
0
|
|
3
|
|
6887
|
method up ($task) { return $self->updown($task, 'up' ); } |
|
0
|
0
|
|
0
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
125
|
3
|
0
|
|
3
|
|
6045
|
method down($task) { return $self->updown($task, 'down'); } |
|
0
|
0
|
|
0
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
|
128
|
3
|
|
|
3
|
|
11370
|
method _update( |
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
|
|
|
|
129
|
0
|
0
|
|
|
|
|
$task!, |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
130
|
0
|
0
|
|
|
|
|
$updates! |
|
0
|
0
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
) { |
132
|
0
|
|
|
|
|
|
my $payload = $self->_encode_json({ |
133
|
|
|
|
|
|
|
%$updates, |
134
|
|
|
|
|
|
|
}); |
135
|
|
|
|
|
|
|
|
136
|
0
|
|
|
|
|
|
my $req = $self->_build_request('PUT', "/user/task/$task"); |
137
|
|
|
|
|
|
|
|
138
|
0
|
|
|
|
|
|
$req->content( $payload ); |
139
|
|
|
|
|
|
|
|
140
|
0
|
|
|
|
|
|
return $self->_request( $req ); |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
# NOTE: We exclude rewards |
145
|
|
|
|
|
|
|
# NOTE: This returns a list of data structures. |
146
|
|
|
|
|
|
|
# NOTE: Case insensitive search |
147
|
|
|
|
|
|
|
|
148
|
3
|
0
|
|
3
|
|
14991
|
method search_tasks($search_term, :$all = 0) { |
|
0
|
0
|
|
0
|
|
|
|
|
0
|
0
|
|
|
|
|
|
|
0
|
0
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
149
|
0
|
|
|
|
|
|
my $tasks = $self->tasks; |
150
|
0
|
|
|
|
|
|
my @matches; |
151
|
|
|
|
|
|
|
my $tag_uuid; |
152
|
|
|
|
|
|
|
|
153
|
0
|
|
|
|
|
|
my $tag_prefix = $self->tag_prefix; |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
# Check to see if we're doing a tag search. |
156
|
|
|
|
|
|
|
|
157
|
0
|
0
|
|
|
|
|
if ($search_term =~ /^\Q$tag_prefix\E(?<tag>.*)/ms) { |
158
|
0
|
0
|
|
|
|
|
if (not $self->tags) { croak "No tags defined on " . ref($self) . " object!"; } |
|
0
|
|
|
|
|
|
|
159
|
3
|
|
|
3
|
|
3600
|
$tag_uuid = $self->tags->{ $+{tag} }; |
|
3
|
|
|
|
|
1611
|
|
|
3
|
|
|
|
|
662
|
|
|
0
|
|
|
|
|
|
|
160
|
0
|
0
|
|
|
|
|
$tag_uuid or croak "Search for unknown tag: $+{tag}"; |
161
|
|
|
|
|
|
|
} |
162
|
|
|
|
|
|
|
|
163
|
0
|
|
|
|
|
|
foreach my $task (@$tasks) { |
164
|
|
|
|
|
|
|
|
165
|
0
|
0
|
|
|
|
|
next if $task->type eq 'reward'; |
166
|
0
|
0
|
0
|
|
|
|
if ($task->completed and not $all) { next; } |
|
0
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
# If we're doing a tag search... |
169
|
0
|
0
|
|
|
|
|
if ($tag_uuid) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
170
|
0
|
0
|
|
|
|
|
next if not $task->tags; # Skip tagless tasks |
171
|
0
|
0
|
|
|
|
|
push(@matches, $task) if $task->tags->{$tag_uuid}; |
172
|
|
|
|
|
|
|
} |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
# If our search term exactly matches a task ID, then use that. |
175
|
|
|
|
|
|
|
elsif ($task->id eq $search_term) { |
176
|
0
|
|
|
|
|
|
return $task; |
177
|
|
|
|
|
|
|
} |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
# Otherwise, if it contains our search term. |
180
|
|
|
|
|
|
|
elsif ($task->text =~ /\Q$search_term\E/i) { |
181
|
0
|
|
|
|
|
|
push(@matches, $task); |
182
|
|
|
|
|
|
|
} |
183
|
|
|
|
|
|
|
} |
184
|
|
|
|
|
|
|
|
185
|
0
|
|
|
|
|
|
return @matches; |
186
|
|
|
|
|
|
|
} |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
#### Internal use only code beyond this point #### |
189
|
|
|
|
|
|
|
|
190
|
3
|
0
|
|
3
|
|
6646
|
method _get_tasks($url) { |
|
0
|
0
|
|
0
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
191
|
0
|
|
|
|
|
|
my $results = $self->_get_request($url); |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
# If we're fetching a single task, it can come back as |
194
|
|
|
|
|
|
|
# an un-wrapped hash. We re-wrap it here if that's the case. |
195
|
|
|
|
|
|
|
|
196
|
0
|
0
|
|
|
|
|
if (ref($results) ne 'ARRAY') { |
197
|
0
|
|
|
|
|
|
$results = [$results]; |
198
|
|
|
|
|
|
|
} |
199
|
|
|
|
|
|
|
|
200
|
0
|
|
|
|
|
|
my @tasks; |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
# Somehow we can get back completely undefined results, |
203
|
|
|
|
|
|
|
# hence the grep to only look at defined ones. |
204
|
|
|
|
|
|
|
|
205
|
0
|
|
|
|
|
|
foreach my $raw (grep { defined } @$results) { |
|
0
|
|
|
|
|
|
|
206
|
0
|
|
|
|
|
|
push @tasks, WebService::HabitRPG::Task->new( |
207
|
|
|
|
|
|
|
$raw, |
208
|
|
|
|
|
|
|
); |
209
|
|
|
|
|
|
|
} |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
# Sort based on task type. The old API used to do this for us. |
212
|
0
|
|
|
|
|
|
@tasks = sort { $a->type cmp $b->type } @tasks; |
|
0
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
|
214
|
0
|
|
|
|
|
|
return \@tasks; |
215
|
|
|
|
|
|
|
} |
216
|
|
|
|
|
|
|
|
217
|
3
|
0
|
|
3
|
|
6691
|
method _get_request($url) { |
|
0
|
0
|
|
0
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
218
|
0
|
|
|
|
|
|
my $req = $self->_build_request('GET', $url); |
219
|
0
|
|
|
|
|
|
return $self->_request( $req ); |
220
|
|
|
|
|
|
|
} |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
# I don't like the name here, but this makes our request, and decodes |
223
|
|
|
|
|
|
|
# the JSON-filled result |
224
|
|
|
|
|
|
|
|
225
|
3
|
0
|
|
3
|
|
6256
|
method _request($req) { |
|
0
|
0
|
|
0
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
226
|
0
|
|
|
|
|
|
return $self->_decode_json($self->agent->request( $req )->decoded_content); |
227
|
|
|
|
|
|
|
} |
228
|
|
|
|
|
|
|
|
229
|
3
|
0
|
|
3
|
|
8571
|
method _build_request($type, $url) { |
|
0
|
0
|
|
0
|
|
|
|
|
0
|
0
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
|
231
|
0
|
0
|
|
|
|
|
warn "Making $type request to $url" if $DEBUG; |
232
|
|
|
|
|
|
|
|
233
|
0
|
|
|
|
|
|
my $req = HTTP::Request->new( $type, $self->api_base . $url ); |
234
|
0
|
|
|
|
|
|
$req->header( 'Content-Type' => 'application/json'); |
235
|
0
|
|
|
|
|
|
$req->header( 'x-api-user' => $self->user_id ); |
236
|
0
|
|
|
|
|
|
$req->header( 'x-api-key' => $self->api_token ); |
237
|
|
|
|
|
|
|
|
238
|
0
|
|
|
|
|
|
return $req; |
239
|
|
|
|
|
|
|
} |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
my $json = JSON::Any->new; |
242
|
|
|
|
|
|
|
|
243
|
3
|
0
|
|
3
|
|
6491
|
method _decode_json($string) { |
|
0
|
0
|
|
0
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
|
245
|
0
|
0
|
|
|
|
|
warn "Decoding JSON: $string" if $DEBUG; |
246
|
|
|
|
|
|
|
|
247
|
0
|
|
|
|
|
|
$self->_last_json($string); # For debugging |
248
|
0
|
|
|
|
|
|
my $result = $json->decode( $string ); |
249
|
|
|
|
|
|
|
|
250
|
0
|
0
|
|
|
|
|
if ($DEBUG) { |
251
|
0
|
|
|
|
|
|
warn "JSON decoded to: ", Dumper($result), "\n"; |
252
|
|
|
|
|
|
|
} |
253
|
|
|
|
|
|
|
|
254
|
0
|
|
|
|
|
|
return $result; |
255
|
|
|
|
|
|
|
} |
256
|
|
|
|
|
|
|
|
257
|
3
|
0
|
|
3
|
|
6226
|
method _encode_json($string) { |
|
0
|
0
|
|
0
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
258
|
0
|
|
|
|
|
|
return $json->encode( $string ); |
259
|
|
|
|
|
|
|
} |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
1; |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
__END__ |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
=pod |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
=encoding UTF-8 |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
=head1 NAME |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
WebService::HabitRPG - Perl interface to the HabitRPG API |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
=head1 VERSION |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
version 0.23 |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
=head1 SYNOPSIS |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
use WebService::HabitRPG; |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
# The API Token and User ID are obained through the |
283
|
|
|
|
|
|
|
# Setting -> API link on http://habitrpg.com/ |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
my $hrpg = WebService::HabitRPG->new( |
286
|
|
|
|
|
|
|
api_token => 'your-token-goes-here', |
287
|
|
|
|
|
|
|
user_id => 'your-user-id-goes-here', |
288
|
|
|
|
|
|
|
tags => { work => $uuid, home => $uuid2, ... }, # optional |
289
|
|
|
|
|
|
|
); |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
# Get everyting about the user |
292
|
|
|
|
|
|
|
my $user = $hrpg->user; |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
# Get all tasks. |
295
|
|
|
|
|
|
|
my $tasks = $hrpg->tasks; |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
# Get all tasks of a particular type (eg: 'daily') |
298
|
|
|
|
|
|
|
my $daily = $hrpg->tasks('daily'); |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
# Increment/decrement a task |
301
|
|
|
|
|
|
|
$hrpg->up($task_id); |
302
|
|
|
|
|
|
|
$hrpg->down($task_id); |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
# Make a new task |
305
|
|
|
|
|
|
|
$hrpg->new_task( |
306
|
|
|
|
|
|
|
type => 'daily', |
307
|
|
|
|
|
|
|
text => 'floss teeth', |
308
|
|
|
|
|
|
|
up => 1, |
309
|
|
|
|
|
|
|
down => 0, |
310
|
|
|
|
|
|
|
); |
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
=head1 DESCRIPTION |
313
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
Interface to API provided by L<HabitRPG|http://habitrpg.com/>. |
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
At the time of release, the HabitRPG API is still under construction. |
317
|
|
|
|
|
|
|
This module may change as a result. |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
Note that when data structures are returned, they are almost |
320
|
|
|
|
|
|
|
always straight conversions from the JSON returned by the |
321
|
|
|
|
|
|
|
HabitRPG API. |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
=head1 METHODS |
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
=head2 new |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
my $hrpg = WebService::HabitRPG->new( |
328
|
|
|
|
|
|
|
api_token => 'your-token-goes-here', |
329
|
|
|
|
|
|
|
user_id => 'your-user-id-goes-here', |
330
|
|
|
|
|
|
|
tags => { work => $work_uuid, home => $home_uuid, ... }, |
331
|
|
|
|
|
|
|
tag_prefix => '^', # Optional, defaults to '^' |
332
|
|
|
|
|
|
|
); |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
Creates a new C<WebService::HabitRPG> object. The C<api_token> and C<user_id> |
335
|
|
|
|
|
|
|
parameters are mandatory. You may also pass your own L<WWW::Mechanize> |
336
|
|
|
|
|
|
|
compatible user-agent with C<agent>, and should you need it your own HabitRPG |
337
|
|
|
|
|
|
|
API base URL with C<api_base> (useful for testing, or if you're running your |
338
|
|
|
|
|
|
|
own server). |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
By default, the official API base of C<https://habitrpg.com/api/v1> is used. |
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
The C<tags> field is optional, but if included should consist of C<tag => uuid> |
343
|
|
|
|
|
|
|
pairs. When API support is added for tags, this optional will become obsolete. |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
I<Use of the tags feature should be considered experimental>. |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
=head2 user |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
my $user = $hrpg->user(); |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
Returns everything from the C</user> route in the HabitRPG API. |
352
|
|
|
|
|
|
|
This is practically everything about the user, their tasks, scores, |
353
|
|
|
|
|
|
|
and other information. |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
The Perl data structure that is returned is a straight conversion |
356
|
|
|
|
|
|
|
from the JSON provided by the HabitRPG API. |
357
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
=head2 tasks |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
my $tasks = $hrpg->tasks(); # All tasks |
361
|
|
|
|
|
|
|
my $habits = $hrpg->tasks('habit'); # Only habits |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
Return a reference to an array of tasks. With no arguments, all |
364
|
|
|
|
|
|
|
tasks (habits, dailies, todos and rewards) are returned. With |
365
|
|
|
|
|
|
|
an argument, only tasks of the given type are returned. The |
366
|
|
|
|
|
|
|
argument must be one of C<habit>, C<daily>, C<todo> or C<reward>. |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
See L<WebService::HabitRPG::Task> for a complete description of |
369
|
|
|
|
|
|
|
what task objects look like. |
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
Not all tasks will have all fields. Using the L<hrpg> command-line |
372
|
|
|
|
|
|
|
tool with C<hrpg dump tasks> is a convenient way to see the |
373
|
|
|
|
|
|
|
data structures returned by this method. |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
=head2 get_task |
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
my $task = $hrpg->get_task('6a11dd4d-c2d6-42b7-b9ff-f562d4ccce4e'); |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
Given a task ID, returns information on that task in the same format |
380
|
|
|
|
|
|
|
at L</tasks> above. |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
=head2 new_task |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
$hrpg->new_task( |
385
|
|
|
|
|
|
|
type => 'daily', # Required |
386
|
|
|
|
|
|
|
text => 'floss teeth', # Required |
387
|
|
|
|
|
|
|
up => 1, # Suggested, defaults true |
388
|
|
|
|
|
|
|
down => 0, # Suggested, defaults true |
389
|
|
|
|
|
|
|
value => 0, |
390
|
|
|
|
|
|
|
note => "Floss every tooth for great justice", |
391
|
|
|
|
|
|
|
completed => 0, |
392
|
|
|
|
|
|
|
extend => {}, |
393
|
|
|
|
|
|
|
); |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
Creates a new task. Only the C<type> and C<text> arguments are |
396
|
|
|
|
|
|
|
required, all other tasks are optional. The C<up> and C<down> |
397
|
|
|
|
|
|
|
options default to true (ie, tasks can be both incremented and |
398
|
|
|
|
|
|
|
decremented). |
399
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
The C<type> parameter must be one of: C<habit>, C<daily>, |
401
|
|
|
|
|
|
|
C<todo> or C<reward>. |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
The C<extend> parameter consists to key/value pairs that will be |
404
|
|
|
|
|
|
|
added to the JSON create packet. This should only be used if you |
405
|
|
|
|
|
|
|
know what you're doing, and wish to take advantage of new or |
406
|
|
|
|
|
|
|
undocumented features in the API. |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
Returns a task data structure of the task created, identical |
409
|
|
|
|
|
|
|
to the L</tasks> method above. |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
Creating tasks that can be neither incremented nor decremented |
412
|
|
|
|
|
|
|
is of dubious usefulness. |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
=head2 updown |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
$hrpg->updown('6a11dd4d-c2d6-42b7-b9ff-f562d4ccce4e', 'up' ); |
417
|
|
|
|
|
|
|
$hrpg->updown('6a11dd4d-c2d6-42b7-b9ff-f562d4ccce4e', 'down'); |
418
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
Moves the habit in the direction specified. Returns a data structure |
420
|
|
|
|
|
|
|
of character status: |
421
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
{ |
423
|
|
|
|
|
|
|
exp => 11, |
424
|
|
|
|
|
|
|
gp => 15.5, |
425
|
|
|
|
|
|
|
hp => 50, |
426
|
|
|
|
|
|
|
lv => 2, |
427
|
|
|
|
|
|
|
delta => 1, |
428
|
|
|
|
|
|
|
} |
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
=head2 up |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
$hrpg->up($task); |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
Convenience method. Equivalent to C<$hrpg->updown($task, 'up')>; |
435
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
=head2 down |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
$hrpg->down($task); |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
Convenience method. Equivalent to C<$hrpg->updown($task, 'down')>; |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
=head2 _update |
443
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
$hrpg->_update($task, { attr => value }); |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
I<This method should be considered experimental.> |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
Updates the given task on the server (using the underlying C<PUT> |
449
|
|
|
|
|
|
|
functionality in the API). Attributes are not checked for sanity, |
450
|
|
|
|
|
|
|
they're just directly converted into JSON. |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
=head2 search_tasks |
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
my @tasks = $hrpg->search_tasks($search_term, all => $bool); |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
# Eg: |
457
|
|
|
|
|
|
|
my @tasks = $hrpg->search_tasks('floss'); |
458
|
|
|
|
|
|
|
my @tasks = $hrpg->search_tasks('git', all => 1); |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
Search for tasks which match the provided search term. If the |
461
|
|
|
|
|
|
|
search term C<exactly> matches a task ID, then the task ID |
462
|
|
|
|
|
|
|
is returned. Otherwise, returns a list of tasks which contain |
463
|
|
|
|
|
|
|
the search term in their names (the C<text> field returned by the API). |
464
|
|
|
|
|
|
|
This list is in the same format as the as the L</tasks> method call. |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
If the term begins with the tag prefix character ('^' by default), |
467
|
|
|
|
|
|
|
it is considered to be a tag, and the hashless form is searched for. |
468
|
|
|
|
|
|
|
For example, '^work' will result in returning all tasks which match |
469
|
|
|
|
|
|
|
the tag 'work'. |
470
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
If the term does not begin with a hash, then the search term is |
472
|
|
|
|
|
|
|
treated in a literal, case-insensitive fashion. |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
If the optional C<all> parameter is set, then all tasks are |
475
|
|
|
|
|
|
|
returned. Otherwise only non-completed tasks are returned. |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
This is useful for providing a human-friendly way to refer to |
478
|
|
|
|
|
|
|
tasks. For example: |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
# Search for a user-provided term |
481
|
|
|
|
|
|
|
my @tasks = $hrpg->search_tasks($term); |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
# Increment task if found |
484
|
|
|
|
|
|
|
if (@tasks == 1) { |
485
|
|
|
|
|
|
|
$hrpg->up($tasks[0]->id); |
486
|
|
|
|
|
|
|
} |
487
|
|
|
|
|
|
|
else { |
488
|
|
|
|
|
|
|
say "Too few or too many tasks found."; |
489
|
|
|
|
|
|
|
} |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
=for Pod::Coverage BUILD DEMOLISH api_token user_id agent api_base tags tag_prefix |
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
=head1 BUGS |
494
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
I'm sure there are plenty! Please view and/or record them at |
496
|
|
|
|
|
|
|
L<https://github.com/pjf/WebService-HabitRPG/issues> . |
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
=head1 SEE ALSO |
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
The L<HabitRPG API spec|https://github.com/lefnire/habitrpg/wiki/API>. |
501
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
The L<hrpg> command-line client. It's freakin' awesome. |
503
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
=head1 AUTHOR |
505
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
Paul Fenwick <pjf@cpan.org> |
507
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
509
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
This software is copyright (c) 2013 by Paul Fenwick. |
511
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
This is free software; you can redistribute it and/or modify it under |
513
|
|
|
|
|
|
|
the same terms as the Perl 5 programming language system itself. |
514
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
=cut |