-
Notifications
You must be signed in to change notification settings - Fork 11
/
craigslist-renew.pl
executable file
·181 lines (160 loc) · 5.13 KB
/
craigslist-renew.pl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#!/usr/bin/env perl
# This script logs in to your craigslist account and renews all posts that can be renewed
# The script should be invoked with a config file in yaml format containing the following info:
# ---
# email: <craigslist login>
# password: <craigslist password>
# notify: <comma separated list of emails>
use strict;
use warnings;
use WWW::Mechanize;
use MIME::Lite;
use HTML::TableExtract;
use Getopt::Long;
use YAML qw( LoadFile );
use List::MoreUtils qw( zip );
use File::Slurp qw( append_file );
my $mech = WWW::Mechanize->new();
my $renewed = 0;
my $check_expired;
GetOptions('expired' => \$check_expired);
defined($ARGV[0]) || die("Usage: $0 [--expired] <config.yml>\n");
my $config = LoadFile($ARGV[0]);
my $NOTIFY = $config->{notify};
my $SUBJECT = "Craigslist Automatic Renewal";
my $url = "https://accounts.craigslist.org/login";
$mech->get($url);
# log in with username and password
$mech->submit_form(
fields => {
inputEmailHandle => $config->{email},
inputPassword => $config->{password},
}
);
# once in a while, Craigslist verifies the login via Captcha
$mech->quiet(1);
if ($mech->form_with_fields('inputEmailHandle', 'inputPassword')) {
notify("Login failed - requires captcha");
exit(1);
}
$mech->quiet(0);
# filter active posts only
$mech->form_id("account-homepage-form");
$mech->click_button(value=>"active");
# if --expired flag was specified, check for expired posts
if ($check_expired) {
$SUBJECT = "Craigslist post expired";
check_expired($mech->content);
exit(0);
}
my $page = 1;
# loop thru all pages
while (1) {
# look for all listings with a renew button
my $n=1;
my @forms;
foreach my $form ($mech->forms) {
if ($form->method() eq "POST") {
my $input = $form->find_input('go');
if ($input && $input->value() eq "renew") {
push @forms, $n;
}
}
$n++;
}
foreach my $form_id (@forms) {
my $form = $mech->form_number($form_id);
# fetch posting link
my $postid = (split('/', $form->action()))[-1];
my $link = $mech->find_link(url_regex => qr/$postid\.html/);
# click the renew button
$mech->submit_form();
if ($mech->content() =~ /This posting has been renewed/) {
notify("Renewed \"" . $link->text() . "\" (" . $link->url() . ")");
$renewed++;
}
else {
notify("Could not renew post - " . $form->action );
}
$mech->back();
# only renew the first posting unless renew_all is specified
last unless $config->{renew_all};
}
$page = $page + 1;
# if there is another page, go there
if ($page > 5) {
# don't go past 5 pages (maybe add config option for this later)
last;
}
elsif ($mech->find_link(text=>$page)) {
$mech->follow_link(text=>$page);
}
else {
last;
}
} # end while
# print message in interactive mode or send email when run from cron
sub notify {
my $message = shift @_;
# append to log, if specified
if ($config->{logfile}) {
my $ts = scalar localtime;
my $email = $config->{email};
my $line = "[$ts] $email: $message\n";
append_file($config->{logfile}, {binmode => ':utf8' }, $line);
}
if (-t STDIN) {
print "$message\n";
}
elsif (!$config->{'no_success_mail'} || $check_expired) {
my $msg = MIME::Lite->new(
To => $NOTIFY,
Subject => $SUBJECT,
Data => $message,
);
if ($config->{from}) {
$msg->add(From => $config->{from});
}
$msg->send;
}
}
# the config file can list postings that should currently be active. This is specified in the config as:
# required:
# - title: My Posting
# area: nyc
# - title: Another posting
# area: nyc
sub check_expired {
my ($content) = @_;
my @columns = qw( status manage title area date id );
my $te = HTML::TableExtract->new(headers=>\@columns);
$te->parse($content);
my @tables = $te->tables;
my $ts = $tables[0];
my $required = $config->{'postings'};
foreach my $row ($ts->rows) {
map {s/^\s*//gm;s/\s*$//gm;s/\n//g;} @$row;
my %info = zip @columns, @$row;
if ($info{'status'} =~ /active/i) {
# check if this posting matches one of the required postings
# if so, mark it as active in the required hash
foreach my $posting (@$required) {
my $title = $posting->{'title'};
my $area = $posting->{'area'} // "";
if ($info{'title'} =~ /\Q$title/i and $info{'area'} =~ /$area/i) {
$posting->{'active'} = 1;
}
}
}
}
my @expired = ();
foreach my $posting (@$required) {
if (!defined($posting->{'active'})) {
push @expired, $posting->{'title'} . ' (' . $posting->{'area'} . ')';
}
}
if (@expired) {
my $msg = "The following posts have expired:\n\n" . join("\n", @expired);
notify($msg);
}
}