| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
# $Id: Poster.pm,v 1.1 2013/12/22 05:33:16 grant Exp $ |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
package WWW::Sitebase::Poster; |
|
4
|
|
|
|
|
|
|
|
|
5
|
2
|
|
|
2
|
|
10217
|
use warnings; |
|
|
2
|
|
|
|
|
9
|
|
|
|
2
|
|
|
|
|
61
|
|
|
6
|
2
|
|
|
2
|
|
7
|
use strict; |
|
|
2
|
|
|
|
|
2
|
|
|
|
2
|
|
|
|
|
38
|
|
|
7
|
2
|
|
|
2
|
|
5
|
use WWW::Sitebase -Base; |
|
|
2
|
|
|
|
|
2
|
|
|
|
2
|
|
|
|
|
15
|
|
|
8
|
2
|
|
|
2
|
|
5214
|
use IO::Prompt; |
|
|
2
|
|
|
2
|
|
3
|
|
|
|
2
|
|
|
2
|
|
30
|
|
|
|
2
|
|
|
|
|
6
|
|
|
|
2
|
|
|
|
|
1
|
|
|
|
2
|
|
|
|
|
52
|
|
|
|
2
|
|
|
|
|
933
|
|
|
|
2
|
|
|
|
|
26489
|
|
|
|
2
|
|
|
|
|
12
|
|
|
9
|
2
|
|
|
2
|
|
93
|
use Carp; |
|
|
2
|
|
|
|
|
2
|
|
|
|
2
|
|
|
|
|
94
|
|
|
10
|
2
|
|
|
2
|
|
7
|
use File::Spec::Functions; |
|
|
2
|
|
|
|
|
3
|
|
|
|
2
|
|
|
|
|
126
|
|
|
11
|
2
|
|
|
2
|
|
1442
|
use List::Compare; |
|
|
2
|
|
|
|
|
27381
|
|
|
|
2
|
|
|
|
|
896
|
|
|
12
|
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
=head1 NAME |
|
14
|
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
WWW::Sitebase::Poster - Base class for web site posting routines |
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
=head1 VERSION |
|
18
|
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
Version 0.4 |
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
=cut |
|
22
|
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
our $VERSION = '0.4'; |
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
package MyPostingModule; |
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
use WWW::Sitebase::Poster -Base; |
|
30
|
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
# Define your options |
|
32
|
|
|
|
|
|
|
sub default_options { |
|
33
|
|
|
|
|
|
|
my $options = super; |
|
34
|
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
$options->{cache_file} = { default => 'mypostingmodule' }; # (VERY IMPORTANT) |
|
36
|
|
|
|
|
|
|
$options->{my_option} = 0; # 0 = not required. 1 means required. |
|
37
|
|
|
|
|
|
|
$options->{my_option} = { default => 'mydefault' }; # Sets a default for your option. |
|
38
|
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
# Some common example options, say for posting messages or comments: |
|
40
|
|
|
|
|
|
|
$options->{subject} = 1; # Require subject |
|
41
|
|
|
|
|
|
|
$options->{message} = 1; # Require a message |
|
42
|
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
return $options; |
|
44
|
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
} |
|
46
|
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
# Add accessors if you like (usually a good idea) |
|
48
|
|
|
|
|
|
|
# (Poster.pm already gives you the cache_file accessor). |
|
49
|
|
|
|
|
|
|
field 'my_option'; |
|
50
|
|
|
|
|
|
|
field 'subject'; |
|
51
|
|
|
|
|
|
|
field 'message'; |
|
52
|
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
# Define your send_post method (see examples below) |
|
54
|
|
|
|
|
|
|
sub send_post { |
|
55
|
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
my ( $friend_id ) = @_; |
|
57
|
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
$result = $self->browser->do_something( $friend_id, $other_value ); |
|
59
|
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
# ... Do anything else you need ... |
|
61
|
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
return $result; # $result must be P, R, F, or undef. (Pass, Retry, Fail, or stop) |
|
63
|
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
} |
|
65
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
---------------- |
|
68
|
|
|
|
|
|
|
Then you or others can write a script that uses your module. |
|
69
|
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
#!/usr/bin/perl -w |
|
71
|
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
use MyPostingModule; |
|
73
|
|
|
|
|
|
|
use WWW::Myspace; |
|
74
|
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
my @friend_list = &fancy_friend_gathering_routine; |
|
76
|
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
my $poster = new MyPostingModule( |
|
78
|
|
|
|
|
|
|
browser => new WWW::Myspace, # Note, this'll prompt for username/password |
|
79
|
|
|
|
|
|
|
friend_ids => \@friend_list, |
|
80
|
|
|
|
|
|
|
subject => 'hi there!', |
|
81
|
|
|
|
|
|
|
message => 'I'm writing you a message!', |
|
82
|
|
|
|
|
|
|
noisy => 1, |
|
83
|
|
|
|
|
|
|
interactive => 1, |
|
84
|
|
|
|
|
|
|
); |
|
85
|
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
$poster->post; |
|
87
|
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
This is a base class for modules that need to post things and remember |
|
89
|
|
|
|
|
|
|
to whom they've posted. |
|
90
|
|
|
|
|
|
|
If you're writing a new module that needs to send something and |
|
91
|
|
|
|
|
|
|
remember stuff about it, you'll want to look at this module. It gives |
|
92
|
|
|
|
|
|
|
you all sorts of neat tools, like write_log and read_log to remember |
|
93
|
|
|
|
|
|
|
what you did, and it automatically parses all your arguments right |
|
94
|
|
|
|
|
|
|
in the new method, and can even read them from a |
|
95
|
|
|
|
|
|
|
config file in CFG or YAML format. All the "new" method stuff it just |
|
96
|
|
|
|
|
|
|
inherits from WWW::Sitebase, so look there for more info. |
|
97
|
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
The cache_file is where write_log and read_log write and read their data. |
|
99
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
You MUST set the cache_file default to something specific to your module. |
|
101
|
|
|
|
|
|
|
This will be used by the cache_file method to return (and create if needed) |
|
102
|
|
|
|
|
|
|
the default cache file for your module. Make sure it's unique to "Poster" modules. |
|
103
|
|
|
|
|
|
|
(Hint: name it after your module). Your default filename will be placed |
|
104
|
|
|
|
|
|
|
in the value returned by $self->cache_dir (.www-poster by default), so don't |
|
105
|
|
|
|
|
|
|
specify a path. If you're writing a WWW::Myspace module, you |
|
106
|
|
|
|
|
|
|
should override cache_dir. See "cache_dir" below. |
|
107
|
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
This module itself is a subclass of WWW::Sitebase, so it inherits |
|
109
|
|
|
|
|
|
|
"new", default_options, and a few other methods from there. Be |
|
110
|
|
|
|
|
|
|
sure to read up on WWW::Sitebase if you're not familiar with it, |
|
111
|
|
|
|
|
|
|
as your class will magically inherit those methods too. |
|
112
|
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
If you're writing a script that uses a subclass of this module, |
|
114
|
|
|
|
|
|
|
you can read up on the methods it provides below. |
|
115
|
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
=cut |
|
117
|
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
=head1 OPTIONS |
|
119
|
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
The following options can be passed to the new method, or set using |
|
121
|
|
|
|
|
|
|
accessor methods (see below). |
|
122
|
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
Note that if you're writing a script using a subclass of this module, |
|
124
|
|
|
|
|
|
|
more options may be available to the specific subclass you're |
|
125
|
|
|
|
|
|
|
using. |
|
126
|
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
Options with sample values: |
|
128
|
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
friend_ids => [ 12345, 123456 ], # Arrayref of friendIDs. |
|
130
|
|
|
|
|
|
|
cache_file => '/path/to/file', |
|
131
|
|
|
|
|
|
|
max_count => 50, # Maximum number of successful posts before stopping |
|
132
|
|
|
|
|
|
|
html => 1, # 1=display in HTML, 0=plain text. |
|
133
|
|
|
|
|
|
|
delay_time => 86400, # Number of seconds to sleep on COUNTER/CAPTCHA |
|
134
|
|
|
|
|
|
|
interactive => 1, # Can we ask questions? Turns on noisy also. |
|
135
|
|
|
|
|
|
|
noisy => 1, # Display detailed output (1) or be quiet (0)? |
|
136
|
|
|
|
|
|
|
browser => $myspace, # A valid, logged-in site browsing object (i.e. WWW::Myspace, |
|
137
|
|
|
|
|
|
|
# or a subclass of WWW::Sitebase::Navigator). |
|
138
|
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
=cut |
|
140
|
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
=head2 default_options |
|
142
|
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
Override this method to allow additional options to be passed to |
|
144
|
|
|
|
|
|
|
"new". You should also provide accessor methods for them. |
|
145
|
|
|
|
|
|
|
These are parsed by Params::Validate. In breif, setting an |
|
146
|
|
|
|
|
|
|
option to "0" means it's optional, "1" means it's required. |
|
147
|
|
|
|
|
|
|
See Params::Validate for more info. It looks like this: |
|
148
|
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
sub default_options { |
|
150
|
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
$self->{default_options} = { |
|
152
|
|
|
|
|
|
|
friend_ids => 0, |
|
153
|
|
|
|
|
|
|
cache_file => 0, |
|
154
|
|
|
|
|
|
|
html => 0, |
|
155
|
|
|
|
|
|
|
browser => 0, |
|
156
|
|
|
|
|
|
|
exclude_my_friends => { default => 0 }, |
|
157
|
|
|
|
|
|
|
interactive => { default => 1 }, |
|
158
|
|
|
|
|
|
|
noisy => { default => 1 }, |
|
159
|
|
|
|
|
|
|
max_count => { default => 0 }, |
|
160
|
|
|
|
|
|
|
}; |
|
161
|
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
return $self->{default_options}; |
|
163
|
|
|
|
|
|
|
} |
|
164
|
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
# So to add a "questions" option that's mandatory: |
|
166
|
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
sub default_options { |
|
168
|
|
|
|
|
|
|
super; |
|
169
|
|
|
|
|
|
|
$self->{default_options}->{questions}=1; |
|
170
|
|
|
|
|
|
|
return $self->{default_options}; |
|
171
|
|
|
|
|
|
|
} |
|
172
|
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
=cut |
|
174
|
|
|
|
|
|
|
|
|
175
|
0
|
|
|
0
|
1
|
|
sub default_options { |
|
176
|
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
$self->{default_options} = { |
|
178
|
0
|
|
|
|
|
|
friend_ids => 0, |
|
179
|
|
|
|
|
|
|
cache_file => 0, |
|
180
|
|
|
|
|
|
|
html => 0, |
|
181
|
|
|
|
|
|
|
browser => 0, |
|
182
|
|
|
|
|
|
|
exclude_my_friends => { default => 0 }, |
|
183
|
|
|
|
|
|
|
interactive => { default => 1 }, |
|
184
|
|
|
|
|
|
|
noisy => { default => 1 }, |
|
185
|
|
|
|
|
|
|
max_count => { default => 0 }, |
|
186
|
|
|
|
|
|
|
}; |
|
187
|
|
|
|
|
|
|
|
|
188
|
0
|
|
|
|
|
|
return $self->{default_options}; |
|
189
|
|
|
|
|
|
|
} |
|
190
|
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
=head2 friend_ids |
|
193
|
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
Retreives/sets the list of friendIDs for whom we're going to |
|
195
|
|
|
|
|
|
|
post things. |
|
196
|
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
$message->friend_ids( 12345, 12347, 123456 ); # Set the list of friends |
|
198
|
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
@friend_ids = $message->friend_ids; # Retreive the list of friends |
|
200
|
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
You can set the friend_ids to a list of friends, an arrayref to a list |
|
202
|
|
|
|
|
|
|
of friends, or to an object whose "get_friends" method will return |
|
203
|
|
|
|
|
|
|
the list of friends. |
|
204
|
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
When called without arguments, returns a list of friends (even if you |
|
206
|
|
|
|
|
|
|
set it with an arrayref). |
|
207
|
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
=cut |
|
209
|
|
|
|
|
|
|
|
|
210
|
0
|
|
|
0
|
1
|
|
sub friend_ids { |
|
211
|
0
|
0
|
|
|
|
|
if ( @_ ) { |
|
212
|
0
|
0
|
|
|
|
|
if ( ref $_[0] ) { |
|
213
|
0
|
|
|
|
|
|
$self->{friend_ids} = $_[0]; |
|
214
|
|
|
|
|
|
|
} else { |
|
215
|
0
|
|
|
|
|
|
$self->{friend_ids} = \@_; |
|
216
|
|
|
|
|
|
|
} |
|
217
|
|
|
|
|
|
|
} else { |
|
218
|
|
|
|
|
|
|
# If $self->{friend_ids} is set, it's either an array ref |
|
219
|
|
|
|
|
|
|
# to a list of friends, or an object that we need to call |
|
220
|
|
|
|
|
|
|
# "get_friends" on, which will return a list of friends. |
|
221
|
0
|
0
|
|
|
|
|
if ( defined ( $self->{friend_ids} ) ) { |
|
222
|
0
|
0
|
|
|
|
|
if ( ref $self->{friend_ids} eq "ARRAY" ) { |
|
223
|
0
|
|
|
|
|
|
return @{ $self->{friend_ids} }; |
|
|
0
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
} else { |
|
225
|
0
|
|
|
|
|
|
return $self->{friend_ids}->get_friends; |
|
226
|
|
|
|
|
|
|
} |
|
227
|
|
|
|
|
|
|
} else { |
|
228
|
0
|
|
|
|
|
|
return (); |
|
229
|
|
|
|
|
|
|
} |
|
230
|
|
|
|
|
|
|
} |
|
231
|
|
|
|
|
|
|
} |
|
232
|
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
=head2 cache_dir |
|
234
|
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
cache_dir sets or returns the directory in which we should store cache |
|
236
|
|
|
|
|
|
|
data. Defaults to $ENV{'HOME'}/.www-poster. |
|
237
|
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
If you're subclassing this module to write a module that will use |
|
239
|
|
|
|
|
|
|
WWW::Myspace, you should override this method with something like: |
|
240
|
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
sub cache_dir { $self->browser->cache_dir( @_ ) } |
|
242
|
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
This will put your module's cache data neatly into the same place as the |
|
244
|
|
|
|
|
|
|
other WWW::Myspace modules' data. |
|
245
|
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
=cut |
|
247
|
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
# Get and scrub the path to their home directory. |
|
249
|
|
|
|
|
|
|
our $HOME_DIR= ""; |
|
250
|
|
|
|
|
|
|
if ( defined $ENV{'HOME'} ) { |
|
251
|
|
|
|
|
|
|
$HOME_DIR = "$ENV{'HOME'}"; |
|
252
|
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
if ( $HOME_DIR =~ /^([\-A-Za-z0-9_ \/\.@\+\\:]*)$/ ) { |
|
254
|
|
|
|
|
|
|
$HOME_DIR = $1; |
|
255
|
|
|
|
|
|
|
} else { |
|
256
|
|
|
|
|
|
|
croak "Invalid characters in $ENV{HOME}."; |
|
257
|
|
|
|
|
|
|
} |
|
258
|
|
|
|
|
|
|
} |
|
259
|
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
field cache_dir => catfile( "$HOME_DIR", '.www-poster' ); |
|
261
|
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
=head2 cache_file |
|
263
|
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
Sets or returns the cache filename. This defaults to |
|
265
|
|
|
|
|
|
|
$self->default_options->{cache_file}->{default} in cache_dir. |
|
266
|
|
|
|
|
|
|
If you try to call cache_file without a value and you haven't set |
|
267
|
|
|
|
|
|
|
default_options properly, it'll get really pissed off and throw nasty |
|
268
|
|
|
|
|
|
|
error messages all over your screen. |
|
269
|
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
For convenience this method returns the value in all cases, so you |
|
271
|
|
|
|
|
|
|
can do this: |
|
272
|
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
$cache_file = $commented->cache_file( "filename" ); |
|
274
|
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
=cut |
|
276
|
|
|
|
|
|
|
|
|
277
|
0
|
|
|
0
|
1
|
|
sub cache_file { |
|
278
|
|
|
|
|
|
|
|
|
279
|
0
|
0
|
|
|
|
|
if ( @_ ) { |
|
|
|
0
|
|
|
|
|
|
|
280
|
0
|
|
|
|
|
|
$self->{cache_file} = shift; |
|
281
|
|
|
|
|
|
|
} elsif (! defined $self->{cache_file} ) { |
|
282
|
|
|
|
|
|
|
# Make the cache directory if it doesn't exist |
|
283
|
0
|
|
|
|
|
|
$self->make_cache_dir; |
|
284
|
0
|
|
|
|
|
|
$self->{cache_file} = $self->default_options->{cache_file}->{default}; |
|
285
|
|
|
|
|
|
|
} |
|
286
|
|
|
|
|
|
|
|
|
287
|
0
|
|
|
|
|
|
return $self->{cache_file}; |
|
288
|
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
} |
|
290
|
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
=head2 cache_path |
|
292
|
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
Returns the full path to the cache_file. |
|
294
|
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
=cut |
|
296
|
|
|
|
|
|
|
|
|
297
|
0
|
|
|
0
|
1
|
|
sub cache_path { |
|
298
|
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
# Make the cache directory if it doesn't exist. |
|
300
|
0
|
|
|
|
|
|
$self->make_cache_dir; |
|
301
|
|
|
|
|
|
|
|
|
302
|
0
|
|
|
|
|
|
return catfile( $self->cache_dir, $self->cache_file ); |
|
303
|
|
|
|
|
|
|
} |
|
304
|
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
=head2 html( [1] [0] ) |
|
306
|
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
Sets to display HTML-friendly output (only really useful with "noisy" |
|
308
|
|
|
|
|
|
|
turned on also). |
|
309
|
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
Call html(1) to display HTML tags (currently just "BR" tags). |
|
311
|
|
|
|
|
|
|
Call html(0) to display plain text. |
|
312
|
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
Text output (html = 0) is enabled by default. |
|
314
|
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
Example |
|
316
|
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
$comment->html( 1 ); |
|
318
|
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
=cut |
|
320
|
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
field html => 0; |
|
322
|
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
=head2 browser |
|
324
|
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
Sets/retreives the site navigation object with which we're logged in. |
|
326
|
|
|
|
|
|
|
You'll probably just pass that info to the new method, but the accessor is here |
|
327
|
|
|
|
|
|
|
if you want to use it. |
|
328
|
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
Hint: To make your module more site-specific, add a convenience method: |
|
330
|
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
sub myspace { $self->browser( @_ ) } |
|
332
|
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
or |
|
334
|
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
sub bebo { $self->browser( @_ ) } |
|
336
|
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
=cut |
|
338
|
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
field 'browser'; |
|
340
|
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
=head2 exclude_my_friends |
|
342
|
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
Sets/retrieves the value of the "exclude_my_friends" flag. |
|
344
|
|
|
|
|
|
|
If set to a true value, the "post" method will exclude the logged-in |
|
345
|
|
|
|
|
|
|
user's friends from the list of friendIDs set in the "friend_ids" method. |
|
346
|
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
This works by calling the "get_friends" method of the browser object. If |
|
348
|
|
|
|
|
|
|
the object stored in "browser" doesn't have a "get_friends" method, the |
|
349
|
|
|
|
|
|
|
"post" routine will die. |
|
350
|
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
Note that getting friends can take some time, so it's best to have your |
|
352
|
|
|
|
|
|
|
friend list properly filtered instead of using this option. But, it's here |
|
353
|
|
|
|
|
|
|
if you need it. |
|
354
|
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
=cut |
|
356
|
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
field 'exclude_my_friends'; |
|
358
|
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
=head2 interactive |
|
360
|
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
If set to 1, allows methods to ask questions by displaying a prompt and |
|
362
|
|
|
|
|
|
|
reading STDIN. Setting to 0 makes the script run non-interactively. |
|
363
|
|
|
|
|
|
|
Setting to 1 automatically sets "noisy" to 1 also. |
|
364
|
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
=cut |
|
366
|
|
|
|
|
|
|
|
|
367
|
0
|
|
|
0
|
1
|
|
sub interactive { |
|
368
|
|
|
|
|
|
|
|
|
369
|
0
|
0
|
|
|
|
|
if ( @_ ) { |
|
370
|
0
|
|
|
|
|
|
( $self->{interactive} ) = @_; |
|
371
|
0
|
0
|
|
|
|
|
if ( $self->{interactive} ) { $self->noisy(1) } |
|
|
0
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
} |
|
373
|
|
|
|
|
|
|
|
|
374
|
0
|
|
|
|
|
|
return $self->{interactive}; |
|
375
|
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
} |
|
377
|
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
=head2 noisy( [1] [0] ) |
|
379
|
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
If set to 1, the module should output status reports for each post. |
|
381
|
|
|
|
|
|
|
This, of course, will vary by module, and you'll probably want to |
|
382
|
|
|
|
|
|
|
document any module-specific output in your module. |
|
383
|
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
If "noisy" is off (0), run silently, unless there is an error, until |
|
385
|
|
|
|
|
|
|
you have to stop. Then you may print a report or status. |
|
386
|
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
noisy is off (0) by default. |
|
388
|
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
=cut |
|
390
|
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
field noisy => 0; |
|
392
|
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
=head2 max_count |
|
394
|
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
Sets or returns the number of posts we should attempt before |
|
396
|
|
|
|
|
|
|
stopping. Default: 0 (don't stop). |
|
397
|
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
This is handy if you want to stop before a CAPTCHA response, or if you |
|
399
|
|
|
|
|
|
|
want to limit your daily posts. Override this to set a default that's |
|
400
|
|
|
|
|
|
|
appropriate for your module (i.e. 50 for a Myspace commenting module) |
|
401
|
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
=cut |
|
403
|
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
field max_count => 0; |
|
405
|
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
=head1 POSTING |
|
407
|
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
=head2 send_post |
|
409
|
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
You must override this method with your posting method. It will be |
|
411
|
|
|
|
|
|
|
called by the "post" method and passed an ID from the list of friend_ids |
|
412
|
|
|
|
|
|
|
(set using the option to the "new" method or using the "friend_ids" accessor method). |
|
413
|
|
|
|
|
|
|
It must return two values: a result code (P, R, F, or undef) and a human-readable |
|
414
|
|
|
|
|
|
|
reason string. The result codes mean "Pass", "Retry", "Fail", and "stop!" respectively, |
|
415
|
|
|
|
|
|
|
and the human-readable reason will be used in the report output when the "post" |
|
416
|
|
|
|
|
|
|
method stops. |
|
417
|
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
Example: |
|
419
|
|
|
|
|
|
|
# Send Myspace group invitations. The send_group_invitation method returns two |
|
420
|
|
|
|
|
|
|
# array references, one of passed IDs and one of failed. We want to retry any |
|
421
|
|
|
|
|
|
|
# failures. |
|
422
|
|
|
|
|
|
|
sub send_post { |
|
423
|
|
|
|
|
|
|
my ( $id ) = @_; |
|
424
|
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
my ( $passed, $failed ) = $self->browser->send_group_invitation( $id ); |
|
426
|
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
# We only passed 1 ID, so if "passed" has anything in it, our ID passed. |
|
428
|
|
|
|
|
|
|
if ( @{ $passed } ) { |
|
429
|
|
|
|
|
|
|
return 'P', 'Invitation Sent'; |
|
430
|
|
|
|
|
|
|
} else { |
|
431
|
|
|
|
|
|
|
return 'R', 'Invitation send failed'; |
|
432
|
|
|
|
|
|
|
} |
|
433
|
|
|
|
|
|
|
} |
|
434
|
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
# Post a comment on Myspace. There are several possible codes post_comment could |
|
436
|
|
|
|
|
|
|
# return, so we want to decide for each whether to retry or not. Also, if we reach a |
|
437
|
|
|
|
|
|
|
# CAPTCHA response, we want to stop. Note that this example assumes your |
|
438
|
|
|
|
|
|
|
# subclass module defined "subject" and "message" accessors. |
|
439
|
|
|
|
|
|
|
sub send_post { |
|
440
|
|
|
|
|
|
|
my ( $id ) = @_; |
|
441
|
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
my $result = $self->browser->post_comment( $id, $self->subject, $self->message ); |
|
443
|
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
if ( $result eq 'P' ) { |
|
445
|
|
|
|
|
|
|
return 'P', 'Passed'; |
|
446
|
|
|
|
|
|
|
} elsif ( $result eq 'FC' ) { |
|
447
|
|
|
|
|
|
|
return undef; |
|
448
|
|
|
|
|
|
|
} elsif ( $result eq 'FN' ) { |
|
449
|
|
|
|
|
|
|
return 'R', "Network error"; |
|
450
|
|
|
|
|
|
|
} elsif ( $result eq 'FF' ) { |
|
451
|
|
|
|
|
|
|
return 'F', 'Person is not your friend'; |
|
452
|
|
|
|
|
|
|
} else { |
|
453
|
|
|
|
|
|
|
return 'R', 'Failed - reason unknown'; |
|
454
|
|
|
|
|
|
|
} |
|
455
|
|
|
|
|
|
|
} |
|
456
|
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
=cut |
|
458
|
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
stub 'send_post'; |
|
460
|
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
=head2 post |
|
462
|
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
This is the main method of the module. It is called to do the actual |
|
464
|
|
|
|
|
|
|
posting. It gathers the friendIDs and loops through them, calling the |
|
465
|
|
|
|
|
|
|
"send_post" method to send each post. It handles logging each post, |
|
466
|
|
|
|
|
|
|
and excluding previously-posted friends. |
|
467
|
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
=cut |
|
469
|
|
|
|
|
|
|
|
|
470
|
0
|
|
|
0
|
1
|
|
sub post { |
|
471
|
|
|
|
|
|
|
|
|
472
|
2
|
|
|
2
|
|
13
|
no strict 'refs'; |
|
|
2
|
|
|
|
|
3
|
|
|
|
2
|
|
|
|
|
498
|
|
|
473
|
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
# Check for browser object |
|
475
|
0
|
0
|
|
|
|
|
croak "Must set a valid browser object before calling post method" |
|
476
|
|
|
|
|
|
|
unless ( $self->browser ); |
|
477
|
|
|
|
|
|
|
|
|
478
|
0
|
|
|
|
|
|
$self->{post_count} = 0; |
|
479
|
0
|
|
|
|
|
|
my ( $result, $reason ); |
|
480
|
0
|
|
|
|
|
|
my ( @friend_list ) = $self->friend_ids; |
|
481
|
|
|
|
|
|
|
|
|
482
|
0
|
|
|
|
|
|
( @friend_list ) = $self->_exclude_friends( @friend_list ); |
|
483
|
|
|
|
|
|
|
|
|
484
|
0
|
0
|
|
|
|
|
unless ( @friend_list ) { $self->_report( "Nothing to process\n" ); return; } |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
|
|
486
|
0
|
|
|
|
|
|
foreach my $id ( @friend_list ) { |
|
487
|
0
|
|
|
|
|
|
( $result, $reason ) = $self->send_post( $id ); |
|
488
|
0
|
0
|
|
|
|
|
last unless ( $result ); |
|
489
|
|
|
|
|
|
|
|
|
490
|
0
|
|
|
|
|
|
$self->_record_result( $id, $result, $reason ); |
|
491
|
0
|
0
|
|
|
|
|
$self->{post_count}++ unless ( $result eq 'R' ); |
|
492
|
|
|
|
|
|
|
|
|
493
|
0
|
0
|
0
|
|
|
|
last if ( $self->max_count && ( $self->{post_count} > $self->max_count ) ); |
|
494
|
|
|
|
|
|
|
} |
|
495
|
|
|
|
|
|
|
|
|
496
|
0
|
|
|
|
|
|
$self->_final_report; |
|
497
|
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
} |
|
499
|
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
=head2 post_count |
|
501
|
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
Returns the current number of successful posts (from the internal |
|
503
|
|
|
|
|
|
|
counter used by the "post" method. |
|
504
|
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
# Pause after every 25th post |
|
506
|
|
|
|
|
|
|
sleep 30 if ( ( $self->post_count % 25 ) == 0 ); |
|
507
|
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
=cut |
|
509
|
|
|
|
|
|
|
|
|
510
|
0
|
|
|
0
|
1
|
|
sub post_count { $self->{post_count} } |
|
|
0
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
|
|
512
|
0
|
|
|
0
|
|
|
sub _record_result { |
|
513
|
0
|
|
|
|
|
|
my ( $friend_id, $result, $reason ) = @_; |
|
514
|
|
|
|
|
|
|
|
|
515
|
0
|
0
|
|
|
|
|
unless ( $result =~ /^[PFR]$/o ) { |
|
516
|
0
|
|
|
|
|
|
croak "Invalid result code: \"$result\".\n". |
|
517
|
|
|
|
|
|
|
"Valid codes are P, R, or F (Pass, Retry, or Fail)."; |
|
518
|
|
|
|
|
|
|
} |
|
519
|
|
|
|
|
|
|
|
|
520
|
0
|
|
|
|
|
|
$self->write_log( { friend_id => $friend_id, status => $result } ); |
|
521
|
0
|
|
|
|
|
|
$self->{reasons}->{$reason}++; |
|
522
|
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
} |
|
524
|
|
|
|
|
|
|
|
|
525
|
0
|
|
|
0
|
|
|
sub _final_report { |
|
526
|
|
|
|
|
|
|
|
|
527
|
2
|
|
|
2
|
|
9
|
no strict 'refs'; |
|
|
2
|
|
|
|
|
2
|
|
|
|
2
|
|
|
|
|
457
|
|
|
528
|
|
|
|
|
|
|
|
|
529
|
0
|
|
|
|
|
|
print "\n\nFinal status report...\n\n######################\n"; |
|
530
|
|
|
|
|
|
|
|
|
531
|
0
|
|
|
|
|
|
foreach my $reason ( keys( %{ $self->{reasons} } ) ) { |
|
|
0
|
|
|
|
|
|
|
|
532
|
0
|
|
|
|
|
|
print $self->{reasons}->{$reason} . " " . $reason; |
|
533
|
|
|
|
|
|
|
} |
|
534
|
|
|
|
|
|
|
|
|
535
|
0
|
|
|
|
|
|
print "\n"; |
|
536
|
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
} |
|
538
|
|
|
|
|
|
|
|
|
539
|
0
|
|
|
0
|
|
|
sub _exclude_friends { |
|
540
|
0
|
|
|
|
|
|
my ( @friend_list ) = @_; |
|
541
|
|
|
|
|
|
|
|
|
542
|
0
|
|
|
|
|
|
my @exclude_list = (); |
|
543
|
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
# Exclude our friends if they asked. |
|
545
|
0
|
0
|
|
|
|
|
if ( $self->{'exclude_my_friends'} ) { |
|
546
|
0
|
|
|
|
|
|
$self->_report("Getting friend IDs to exclude. This could take a while.\n"); |
|
547
|
0
|
|
|
|
|
|
push @exclude_list, $self->browser->get_friends; |
|
548
|
|
|
|
|
|
|
} |
|
549
|
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
# Exclude previous posts |
|
551
|
0
|
|
|
|
|
|
$self->_report( "Retreiving list of previous posts\n" ); |
|
552
|
0
|
|
|
|
|
|
push @exclude_list, $self->read_posted('all'); |
|
553
|
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
# Process the exclusions |
|
555
|
0
|
|
|
|
|
|
$self->_report( "Processing exclusions...\n" ); |
|
556
|
0
|
|
|
|
|
|
my $lc = List::Compare->new( |
|
557
|
|
|
|
|
|
|
{ |
|
558
|
|
|
|
|
|
|
lists => [ \@exclude_list, \@friend_list], |
|
559
|
|
|
|
|
|
|
accelerated => 1, # Only one comparison |
|
560
|
|
|
|
|
|
|
unsorted => 1, # Unsorted |
|
561
|
|
|
|
|
|
|
} |
|
562
|
|
|
|
|
|
|
); |
|
563
|
|
|
|
|
|
|
|
|
564
|
0
|
|
|
|
|
|
return ( $lc->get_complement ); |
|
565
|
|
|
|
|
|
|
|
|
566
|
|
|
|
|
|
|
} |
|
567
|
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
=head1 LOGGING METHODS |
|
569
|
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
=head2 reset_log( [ $filter ] ) |
|
571
|
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
Resets the log file. If passed a subroutine reference in $filter, |
|
573
|
|
|
|
|
|
|
items matching filter will be left in the log - everything else will |
|
574
|
|
|
|
|
|
|
be erased. |
|
575
|
|
|
|
|
|
|
|
|
576
|
|
|
|
|
|
|
Say for example you wanted to retry all "Failed" items: |
|
577
|
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
$filter = sub { ( $_->{'status'} eq "P" ) }; |
|
579
|
|
|
|
|
|
|
$self->reset_log( $filter ); |
|
580
|
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
To delete the log file completely, just do: |
|
582
|
|
|
|
|
|
|
|
|
583
|
|
|
|
|
|
|
$self->reset_log; |
|
584
|
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
=cut |
|
586
|
|
|
|
|
|
|
|
|
587
|
0
|
|
|
0
|
1
|
|
sub reset_log { |
|
588
|
|
|
|
|
|
|
|
|
589
|
0
|
|
|
|
|
|
my ( $filter ) = @_; |
|
590
|
|
|
|
|
|
|
|
|
591
|
0
|
0
|
|
|
|
|
unless ( defined $filter ) { |
|
592
|
0
|
0
|
|
|
|
|
unlink $self->cache_path or croak @!; |
|
593
|
0
|
|
|
|
|
|
$self->{log} = undef; |
|
594
|
|
|
|
|
|
|
} else { |
|
595
|
|
|
|
|
|
|
# Read in the items to save |
|
596
|
0
|
|
|
|
|
|
$self->read_log( $filter ); |
|
597
|
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
# Write that to the exclusions file. |
|
599
|
0
|
|
|
|
|
|
$self->write_log('all'); |
|
600
|
|
|
|
|
|
|
} |
|
601
|
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
} |
|
603
|
|
|
|
|
|
|
|
|
604
|
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
#--------------------------------------------------------------------- |
|
606
|
|
|
|
|
|
|
|
|
607
|
|
|
|
|
|
|
=head2 write_log( 'all' | $data ) |
|
608
|
|
|
|
|
|
|
|
|
609
|
|
|
|
|
|
|
If called with "all", write $self->{log} to the log file. |
|
610
|
|
|
|
|
|
|
If called with a hash of data, append a line to the log |
|
611
|
|
|
|
|
|
|
file. |
|
612
|
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
$self->write_log( 'all' ); |
|
614
|
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
$self->write_log( { |
|
616
|
|
|
|
|
|
|
friend_id => $friend_id, |
|
617
|
|
|
|
|
|
|
status => $status |
|
618
|
|
|
|
|
|
|
} ); |
|
619
|
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
If there is a "time" field in the list of log_fields (there is by default), |
|
621
|
|
|
|
|
|
|
write_log will automatically write the current time (the value returned by |
|
622
|
|
|
|
|
|
|
the "time" function) to the file. |
|
623
|
|
|
|
|
|
|
|
|
624
|
|
|
|
|
|
|
=cut |
|
625
|
|
|
|
|
|
|
|
|
626
|
|
|
|
|
|
|
sub write_log |
|
627
|
0
|
|
|
0
|
1
|
|
{ |
|
628
|
2
|
|
|
2
|
|
8
|
no strict 'refs'; |
|
|
2
|
|
|
|
|
3
|
|
|
|
2
|
|
|
|
|
308
|
|
|
629
|
0
|
|
|
|
|
|
my ( $data ) = @_; |
|
630
|
|
|
|
|
|
|
|
|
631
|
0
|
|
|
|
|
|
my ( $fh, $key_field, $key_value ); |
|
632
|
|
|
|
|
|
|
# We track who we've posted to in a file. We need to |
|
633
|
|
|
|
|
|
|
# open and close it each time to make sure everyone |
|
634
|
|
|
|
|
|
|
# gets stored. |
|
635
|
0
|
0
|
|
|
|
|
if ( $data eq 'all' ) { |
|
636
|
|
|
|
|
|
|
# Re-write the file (called by reset_exclusions). |
|
637
|
|
|
|
|
|
|
# ($fh closes when it goes out of scope) |
|
638
|
0
|
0
|
|
|
|
|
open( $fh, ">", $self->cache_path ) or croak @!; |
|
639
|
0
|
|
|
|
|
|
foreach $key_value ( sort( keys( %{ $self->{log} } ) ) ) { |
|
|
0
|
|
|
|
|
|
|
|
640
|
0
|
|
|
|
|
|
$self->$print_row( $key_value, $fh ); |
|
641
|
|
|
|
|
|
|
} |
|
642
|
|
|
|
|
|
|
} else { |
|
643
|
|
|
|
|
|
|
# Just append the current data. |
|
644
|
|
|
|
|
|
|
# ($fh closes when it goes out of scope) |
|
645
|
0
|
0
|
|
|
|
|
open( $fh, ">>", $self->cache_path ) or croak @!; |
|
646
|
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
# Write the data into the log hash |
|
648
|
0
|
|
|
|
|
|
$key_field = $self->log_fields->[0]; # i.e. "friend_id" |
|
649
|
0
|
|
|
|
|
|
$key_value = $data->{"$key_field"}; # i.e. "12345" |
|
650
|
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
# Add the time if it's not there |
|
652
|
0
|
0
|
|
|
|
|
unless ( exists $data->{'time'} ) { |
|
653
|
0
|
|
|
|
|
|
$data->{'time'} = time; |
|
654
|
|
|
|
|
|
|
} |
|
655
|
|
|
|
|
|
|
# Store the rest of the passed data into the log hash. |
|
656
|
0
|
|
|
|
|
|
$self->{'log'}->{$key_value} = $data; |
|
657
|
|
|
|
|
|
|
|
|
658
|
|
|
|
|
|
|
# Write that row to the log file. |
|
659
|
0
|
|
|
|
|
|
$self->$print_row( $key_value, $fh ); |
|
660
|
|
|
|
|
|
|
} |
|
661
|
|
|
|
|
|
|
|
|
662
|
|
|
|
|
|
|
} |
|
663
|
|
|
|
|
|
|
|
|
664
|
|
|
|
|
|
|
# print_row( $row_key, $fh ); |
|
665
|
|
|
|
|
|
|
# Print the row of data from the log hash specified by $row_key to the |
|
666
|
|
|
|
|
|
|
# file identified by the filehandle reference $fh. |
|
667
|
|
|
|
|
|
|
|
|
668
|
|
|
|
|
|
|
my sub print_row { |
|
669
|
|
|
|
|
|
|
|
|
670
|
2
|
|
|
2
|
|
27
|
no strict 'refs'; |
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
231
|
|
|
671
|
|
|
|
|
|
|
my ( $row_key, $fh ) = @_; |
|
672
|
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
# Assemble the row |
|
674
|
|
|
|
|
|
|
my $row = ""; |
|
675
|
|
|
|
|
|
|
foreach my $fieldname ( @{ $self->log_fields } ) { |
|
676
|
|
|
|
|
|
|
( $row ) && ( $row .= ":" ); |
|
677
|
|
|
|
|
|
|
$row .= $self->{log}->{$row_key}->{"$fieldname"}; |
|
678
|
|
|
|
|
|
|
} |
|
679
|
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
# Print to the file |
|
681
|
|
|
|
|
|
|
print $fh "$row\n"; |
|
682
|
|
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
} |
|
685
|
|
|
|
|
|
|
|
|
686
|
|
|
|
|
|
|
=head2 log_fields |
|
687
|
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
Returns a reference to an array of the columnn names you use in your |
|
689
|
|
|
|
|
|
|
log file. Defaults to friend_id, status, and time. The first field |
|
690
|
|
|
|
|
|
|
will be used as your unique key field. |
|
691
|
|
|
|
|
|
|
|
|
692
|
|
|
|
|
|
|
Override this method if you want to use different columns in your |
|
693
|
|
|
|
|
|
|
log file. |
|
694
|
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
=cut |
|
696
|
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
const 'log_fields' => [ 'friend_id', 'status', 'time' ]; |
|
698
|
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
|
|
700
|
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
|
702
|
|
|
|
|
|
|
|
|
703
|
|
|
|
|
|
|
=head2 read_log |
|
704
|
|
|
|
|
|
|
|
|
705
|
|
|
|
|
|
|
Read items from the log file. The first time it's invoked, it |
|
706
|
|
|
|
|
|
|
reads the log file contents into $self->{log}, which is also |
|
707
|
|
|
|
|
|
|
neatly maintained by write_log. This lets you call read_log |
|
708
|
|
|
|
|
|
|
without worrying about huge performance losses, and also |
|
709
|
|
|
|
|
|
|
makes it extendable to use SQL in the future. |
|
710
|
|
|
|
|
|
|
|
|
711
|
|
|
|
|
|
|
For future compatibility, you should access the log only through |
|
712
|
|
|
|
|
|
|
read_log (i.e. don't access $self->{log} directly). |
|
713
|
|
|
|
|
|
|
|
|
714
|
|
|
|
|
|
|
# Post something unless we've successfully posted before |
|
715
|
|
|
|
|
|
|
unless ( $self->read_log("$friend_id")->{'status'} =~ /^P/ ) { |
|
716
|
|
|
|
|
|
|
$myspace->post_something( $friend_id ) |
|
717
|
|
|
|
|
|
|
} |
|
718
|
|
|
|
|
|
|
|
|
719
|
|
|
|
|
|
|
# When did we last post to $friend_id? |
|
720
|
|
|
|
|
|
|
$last_time = $self->read_log("$friend_id")->{'time'}; |
|
721
|
|
|
|
|
|
|
|
|
722
|
|
|
|
|
|
|
if ( $last_time ) { |
|
723
|
|
|
|
|
|
|
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = |
|
724
|
|
|
|
|
|
|
localtime($last_time); |
|
725
|
|
|
|
|
|
|
print "Successfully posted to $friend_id on: " . |
|
726
|
|
|
|
|
|
|
"$mon/$day/$year at $hour:$min:sec\n";; |
|
727
|
|
|
|
|
|
|
} else { |
|
728
|
|
|
|
|
|
|
print "I don't remember posting to $friend_id before\n"; |
|
729
|
|
|
|
|
|
|
} |
|
730
|
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
read_log can be called with an optional filter argument, which can |
|
732
|
|
|
|
|
|
|
be the string "all", or a reference to a subroutine that will |
|
733
|
|
|
|
|
|
|
be used to filter the returned values. The subroutine will be |
|
734
|
|
|
|
|
|
|
passed a hashref of fieldnames and values, by default: |
|
735
|
|
|
|
|
|
|
|
|
736
|
|
|
|
|
|
|
{ friend_id => 12345, |
|
737
|
|
|
|
|
|
|
status => P, |
|
738
|
|
|
|
|
|
|
time => time in 'time' format |
|
739
|
|
|
|
|
|
|
} |
|
740
|
|
|
|
|
|
|
|
|
741
|
|
|
|
|
|
|
This lets you do things like this: |
|
742
|
|
|
|
|
|
|
|
|
743
|
|
|
|
|
|
|
# Reload the cache in memory ($self->{log}) |
|
744
|
|
|
|
|
|
|
$self->read_log( 'all' ) |
|
745
|
|
|
|
|
|
|
|
|
746
|
|
|
|
|
|
|
# Return a list of friends that we've already posted |
|
747
|
|
|
|
|
|
|
# ("the 'o' flag means to optimize the RE because the RE is a constant). |
|
748
|
|
|
|
|
|
|
my $filter = sub { if ( $_->{'status'} =~ /^[PF]$/o ) { 1 } else { 0 } } |
|
749
|
|
|
|
|
|
|
@posted_friends = $self->read_log( $filter ); |
|
750
|
|
|
|
|
|
|
|
|
751
|
|
|
|
|
|
|
# Of course, that's just for example - you'd really do this: |
|
752
|
|
|
|
|
|
|
@posted_friends = $self->read_log( sub { ( $_[0]->{'status'} =~ /^[PF]$/o ) } ); |
|
753
|
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
# or this, which means "return anything that doesn't need to be retried" |
|
755
|
|
|
|
|
|
|
# (this is, in fact, what "read_posted" (see below) does). |
|
756
|
|
|
|
|
|
|
@posted_friends = $self->read_log( sub { ( $_[0]->{'status'} ne 'R' ) } ); |
|
757
|
|
|
|
|
|
|
|
|
758
|
|
|
|
|
|
|
Only the last post attempt for each key (friend_id by default) is stored |
|
759
|
|
|
|
|
|
|
in $self->{log}. It is possible for the cache file to have more than one |
|
760
|
|
|
|
|
|
|
in some circumstances, but only the last will be used, and if the file |
|
761
|
|
|
|
|
|
|
is re-written, previous entries will be erased. |
|
762
|
|
|
|
|
|
|
|
|
763
|
|
|
|
|
|
|
=cut |
|
764
|
|
|
|
|
|
|
|
|
765
|
0
|
|
|
0
|
1
|
|
sub read_log { |
|
766
|
|
|
|
|
|
|
|
|
767
|
2
|
|
|
2
|
|
7
|
no strict 'refs'; |
|
|
2
|
|
|
|
|
2
|
|
|
|
2
|
|
|
|
|
890
|
|
|
768
|
0
|
|
|
|
|
|
my $filter = ""; |
|
769
|
0
|
0
|
|
|
|
|
( $filter ) = @_ if ( @_ ); |
|
770
|
|
|
|
|
|
|
|
|
771
|
0
|
|
|
|
|
|
my $status = ""; |
|
772
|
0
|
|
|
|
|
|
my $id; |
|
773
|
|
|
|
|
|
|
my @values; |
|
774
|
|
|
|
|
|
|
|
|
775
|
|
|
|
|
|
|
# If we haven't read the log file yet, do it. |
|
776
|
0
|
0
|
0
|
|
|
|
unless ( ( defined $self->{log} ) && ( $filter ne 'all' ) ) { |
|
777
|
|
|
|
|
|
|
|
|
778
|
0
|
0
|
|
|
|
|
if ( -f $self->cache_path ) { |
|
779
|
0
|
0
|
|
|
|
|
open( LOGGED, "<", $self->cache_path ) or croak |
|
780
|
|
|
|
|
|
|
"Can't read cache file: " . $self->cache_path . "\n"; |
|
781
|
|
|
|
|
|
|
} else { |
|
782
|
0
|
|
|
|
|
|
$self->{log} = {}; |
|
783
|
0
|
|
|
|
|
|
return $self->{log}; |
|
784
|
|
|
|
|
|
|
} |
|
785
|
|
|
|
|
|
|
|
|
786
|
|
|
|
|
|
|
# There's a cache file, so read it |
|
787
|
0
|
|
|
|
|
|
while ( $id = ) { |
|
788
|
0
|
|
|
|
|
|
chomp $id; |
|
789
|
0
|
|
|
|
|
|
( @values ) = split( ":", $id ); |
|
790
|
|
|
|
|
|
|
|
|
791
|
|
|
|
|
|
|
# Match the values to the appropriate fieldnames |
|
792
|
0
|
|
|
|
|
|
my $i = 0; |
|
793
|
0
|
|
|
|
|
|
my %data = (); |
|
794
|
0
|
|
|
|
|
|
foreach my $value ( @values ) { |
|
795
|
0
|
|
|
|
|
|
my $fieldname = $self->log_fields->["$i"]; |
|
796
|
0
|
|
|
|
|
|
$data{"$fieldname"}=$value; |
|
797
|
0
|
|
|
|
|
|
$i++; |
|
798
|
|
|
|
|
|
|
} |
|
799
|
|
|
|
|
|
|
|
|
800
|
0
|
|
|
|
|
|
$self->{'log'}->{"$values[0]"} = { %data }; |
|
801
|
|
|
|
|
|
|
|
|
802
|
|
|
|
|
|
|
} |
|
803
|
|
|
|
|
|
|
|
|
804
|
0
|
|
|
|
|
|
close LOGGED; |
|
805
|
|
|
|
|
|
|
|
|
806
|
|
|
|
|
|
|
} |
|
807
|
|
|
|
|
|
|
|
|
808
|
|
|
|
|
|
|
# If we reloaded, we're done. |
|
809
|
0
|
0
|
|
|
|
|
return $self->{log} if ( $filter eq 'all' ); |
|
810
|
|
|
|
|
|
|
|
|
811
|
|
|
|
|
|
|
# If they passed a specific key value instead of a filter subroutine, |
|
812
|
|
|
|
|
|
|
# return the appropriate record if it exists. |
|
813
|
0
|
0
|
0
|
|
|
|
if ( ( $filter ) && ( ! ref $filter ) ) { |
|
814
|
0
|
0
|
|
|
|
|
if ( exists $self->{log}->{"$filter"} ) { |
|
815
|
0
|
|
|
|
|
|
return $self->{log}->{$filter} |
|
816
|
|
|
|
|
|
|
} else { |
|
817
|
0
|
|
|
|
|
|
return ""; |
|
818
|
|
|
|
|
|
|
} |
|
819
|
|
|
|
|
|
|
} |
|
820
|
|
|
|
|
|
|
|
|
821
|
|
|
|
|
|
|
# Unless we've got a real filter, return. |
|
822
|
0
|
0
|
|
|
|
|
unless ( ref $filter ) { |
|
823
|
0
|
|
|
|
|
|
return $self->{log}; |
|
824
|
|
|
|
|
|
|
} |
|
825
|
|
|
|
|
|
|
|
|
826
|
|
|
|
|
|
|
# Return a list of keys that matches their filter |
|
827
|
0
|
|
|
|
|
|
my @keys = (); |
|
828
|
0
|
|
|
|
|
|
foreach my $key_value ( sort( keys( %{ $self->{log} } ) ) ) { |
|
|
0
|
|
|
|
|
|
|
|
829
|
0
|
0
|
|
|
|
|
if ( &$filter( $self->{log}->{"$key_value"} ) ) { |
|
830
|
0
|
|
|
|
|
|
push( @keys, $key_value ); |
|
831
|
|
|
|
|
|
|
} |
|
832
|
|
|
|
|
|
|
} |
|
833
|
|
|
|
|
|
|
|
|
834
|
0
|
|
|
|
|
|
return ( @keys ); |
|
835
|
|
|
|
|
|
|
|
|
836
|
|
|
|
|
|
|
} |
|
837
|
|
|
|
|
|
|
|
|
838
|
|
|
|
|
|
|
=head2 read_posted |
|
839
|
|
|
|
|
|
|
|
|
840
|
|
|
|
|
|
|
Returns the keys of all posted rows (status isn't "R"). |
|
841
|
|
|
|
|
|
|
|
|
842
|
|
|
|
|
|
|
my @posted_friends = $self->read_posted; |
|
843
|
|
|
|
|
|
|
|
|
844
|
|
|
|
|
|
|
=cut |
|
845
|
|
|
|
|
|
|
|
|
846
|
0
|
|
|
0
|
1
|
|
sub read_posted { |
|
847
|
|
|
|
|
|
|
|
|
848
|
0
|
|
|
0
|
|
|
return ( $self->read_log( sub { ( $_[0]->{'status'} ne 'R' ) } ) ); |
|
|
0
|
|
|
|
|
|
|
|
849
|
|
|
|
|
|
|
|
|
850
|
|
|
|
|
|
|
} |
|
851
|
|
|
|
|
|
|
|
|
852
|
|
|
|
|
|
|
=head2 previously_posted( $friend_id ) |
|
853
|
|
|
|
|
|
|
|
|
854
|
|
|
|
|
|
|
This convenience method returns true if there's a log entry for |
|
855
|
|
|
|
|
|
|
a previous successful posting. A posting is considered successful |
|
856
|
|
|
|
|
|
|
if the status code is "P" or "F". |
|
857
|
|
|
|
|
|
|
|
|
858
|
|
|
|
|
|
|
unless ( $self->previously_posted( $friend_id ) ) { |
|
859
|
|
|
|
|
|
|
$self->post( $friend_id ); |
|
860
|
|
|
|
|
|
|
} |
|
861
|
|
|
|
|
|
|
|
|
862
|
|
|
|
|
|
|
=cut |
|
863
|
|
|
|
|
|
|
|
|
864
|
0
|
|
|
0
|
1
|
|
sub previously_posted { |
|
865
|
|
|
|
|
|
|
|
|
866
|
0
|
|
|
|
|
|
return ( $self->read_log( $_[0] )->{'status'} ne 'R' ); |
|
867
|
|
|
|
|
|
|
|
|
868
|
|
|
|
|
|
|
} |
|
869
|
|
|
|
|
|
|
|
|
870
|
0
|
|
|
0
|
|
|
sub _report { |
|
871
|
|
|
|
|
|
|
|
|
872
|
0
|
0
|
|
|
|
|
print @_ if $self->{'interactive'}; |
|
873
|
|
|
|
|
|
|
|
|
874
|
|
|
|
|
|
|
} |
|
875
|
|
|
|
|
|
|
|
|
876
|
|
|
|
|
|
|
=head2 make_cache_dir |
|
877
|
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
Creates the cache directory in cache_dir. Only creates the |
|
879
|
|
|
|
|
|
|
top-level directory, croaks if it can't create it. |
|
880
|
|
|
|
|
|
|
|
|
881
|
|
|
|
|
|
|
$myspace->cache_dir("/path/to/dir"); |
|
882
|
|
|
|
|
|
|
$myspace->make_cache_dir; |
|
883
|
|
|
|
|
|
|
|
|
884
|
|
|
|
|
|
|
This function mainly exists for the internal login method to use, |
|
885
|
|
|
|
|
|
|
and for related sub-modules that store their cache files by |
|
886
|
|
|
|
|
|
|
default in WWW:Myspace's cache directory. |
|
887
|
|
|
|
|
|
|
|
|
888
|
|
|
|
|
|
|
=cut |
|
889
|
|
|
|
|
|
|
|
|
890
|
0
|
|
|
0
|
1
|
|
sub make_cache_dir { |
|
891
|
|
|
|
|
|
|
|
|
892
|
|
|
|
|
|
|
# Make the cache directory if it doesn't exist. |
|
893
|
0
|
0
|
|
|
|
|
unless ( -d $self->cache_dir ) { |
|
894
|
0
|
0
|
|
|
|
|
mkdir $self->cache_dir or croak "Can't create cache directory ". |
|
895
|
|
|
|
|
|
|
$self->cache_dir; |
|
896
|
|
|
|
|
|
|
} |
|
897
|
|
|
|
|
|
|
|
|
898
|
|
|
|
|
|
|
} |
|
899
|
|
|
|
|
|
|
|
|
900
|
|
|
|
|
|
|
# This tells Sitebase we don't want to save the browser field. |
|
901
|
0
|
|
|
0
|
|
|
sub _nosave { |
|
902
|
0
|
|
|
|
|
|
my ( $key ) = shift; |
|
903
|
|
|
|
|
|
|
|
|
904
|
0
|
0
|
0
|
|
|
|
if ( $key && ( $key eq 'browser' ) ) { return 0 } |
|
|
0
|
|
|
|
|
|
|
|
905
|
0
|
|
|
|
|
|
return 1; |
|
906
|
|
|
|
|
|
|
|
|
907
|
|
|
|
|
|
|
} |
|
908
|
|
|
|
|
|
|
|
|
909
|
|
|
|
|
|
|
=pod |
|
910
|
|
|
|
|
|
|
|
|
911
|
|
|
|
|
|
|
=head1 AUTHOR |
|
912
|
|
|
|
|
|
|
|
|
913
|
|
|
|
|
|
|
Grant Grueninger, C<< >> |
|
914
|
|
|
|
|
|
|
|
|
915
|
|
|
|
|
|
|
=head1 BUGS |
|
916
|
|
|
|
|
|
|
|
|
917
|
|
|
|
|
|
|
Please report any bugs or feature requests to |
|
918
|
|
|
|
|
|
|
C, or through the web interface at |
|
919
|
|
|
|
|
|
|
L. |
|
920
|
|
|
|
|
|
|
I will be notified, and then you'll automatically be notified of progress on |
|
921
|
|
|
|
|
|
|
your bug as I make changes. |
|
922
|
|
|
|
|
|
|
|
|
923
|
|
|
|
|
|
|
|
|
924
|
|
|
|
|
|
|
=head1 SUPPORT |
|
925
|
|
|
|
|
|
|
|
|
926
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
|
927
|
|
|
|
|
|
|
|
|
928
|
|
|
|
|
|
|
perldoc WWW::Sitebase::Poster |
|
929
|
|
|
|
|
|
|
|
|
930
|
|
|
|
|
|
|
You can also look for information at: |
|
931
|
|
|
|
|
|
|
|
|
932
|
|
|
|
|
|
|
=over 4 |
|
933
|
|
|
|
|
|
|
|
|
934
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
|
935
|
|
|
|
|
|
|
|
|
936
|
|
|
|
|
|
|
L |
|
937
|
|
|
|
|
|
|
|
|
938
|
|
|
|
|
|
|
=item * CPAN Ratings |
|
939
|
|
|
|
|
|
|
|
|
940
|
|
|
|
|
|
|
L |
|
941
|
|
|
|
|
|
|
|
|
942
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker |
|
943
|
|
|
|
|
|
|
|
|
944
|
|
|
|
|
|
|
L |
|
945
|
|
|
|
|
|
|
|
|
946
|
|
|
|
|
|
|
=item * Search CPAN |
|
947
|
|
|
|
|
|
|
|
|
948
|
|
|
|
|
|
|
L |
|
949
|
|
|
|
|
|
|
|
|
950
|
|
|
|
|
|
|
=back |
|
951
|
|
|
|
|
|
|
|
|
952
|
|
|
|
|
|
|
=head1 ACKNOWLEDGEMENTS |
|
953
|
|
|
|
|
|
|
|
|
954
|
|
|
|
|
|
|
=head1 COPYRIGHT & LICENSE |
|
955
|
|
|
|
|
|
|
|
|
956
|
|
|
|
|
|
|
Copyright 2006 Grant Grueninger, all rights reserved. |
|
957
|
|
|
|
|
|
|
|
|
958
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it |
|
959
|
|
|
|
|
|
|
under the same terms as Perl itself. |
|
960
|
|
|
|
|
|
|
|
|
961
|
|
|
|
|
|
|
=cut |
|
962
|
|
|
|
|
|
|
|
|
963
|
|
|
|
|
|
|
1; # End of WWW::Sitebase::Poster |