Initial commit
[webservice-vichan.git] / lib / WebService / Vichan.pm
CommitLineData
b1833b67
MG
1package WebService::Vichan;
2
3use 5.014000;
4use strict;
5use warnings;
6use parent qw/Exporter/;
7
8use HTTP::Tiny;
9use Hash::Inflator;
10use JSON::MaybeXS;
11use Time::HiRes qw/time sleep/;
12
13our $VERSION = '0.001';
14
15our %cache;
16our $last_request = 0;
17our $ht = HTTP::Tiny->new(
18 agent => 'WebService-Vichan/'.$VERSION,
19 verify_SSL => 1
20);
21
22use constant +{
23 API_4CHAN => 'https://a.4cdn.org',
24 API_8CHAN => 'https://8ch.net',
25};
26
27our @EXPORT_OK = qw/API_4CHAN API_8CHAN/;
28our %EXPORT_TAGS = ( all => \@EXPORT_OK );
29
30sub new {
31 my ($class, $url) = @_;
32 bless { url => $url }, $class
33}
34
35sub do_request {
36 my ($url, $cached_result, $cached_timestamp) = @_;
37 my %options;
38 if ($cached_timestamp) {
39 $options{headers}{'If-Modified-Since'} = $cached_timestamp
40 }
41 my $time_since_last_request = time - $last_request;
42 sleep 1 - $time_since_last_request if $time_since_last_request < 1;
43 my $result = $ht->get($url, \%options);
44 $last_request = time;
45 if ($result->{status} == 304) {
46 [$cached_result, $cached_timestamp]
47 } elsif (!$result->{success}) {
48 my $diestr = sprintf "Error requesting %s: %s\n", $url, $result->{reason};
49 die $diestr unless $result->{success};
50 } else {
51 [$result->{content}, $last_request]
52 }
53}
54
55sub requestf {
56 my ($self, $format, @args) = @_;
57 my $what = sprintf $format, @args;
58 my $url = $self->{url} . '/' . $what;
59 my $result = $cache{$url};
60 if (!defined $result) {
61 $cache{$url} = do_request $url
62 } elsif (time - $result->[1] > 10) {
63 $cache{$url} = do_request $url, @$result
64 }
65 decode_json $cache{$url}->[0]
66}
67
68sub boards {
69 my ($self) = @_;
70 my $result = $self->requestf('boards.json');
71 $result = $result->{boards} if ref $result eq 'HASH';
72 my @results = map {
73 $_->{board} //= $_->{uri};
74 Hash::Inflator->new($_)
75 } @$result;
76 wantarray ? @results : \@results;
77}
78
79sub threads {
80 my ($self, $board) = @_;
81 $board = $board->{board} if ref $board;
82 my $result = $self->requestf('%s/threads.json', $board);
83 my @pages = map { Hash::Inflator->new($_) } @$result;
84 wantarray ? @pages : \@pages
85}
86
87sub threads_flat {
88 my @pages = shift->threads(@_);
89 my @flat = map { @{$_->{threads}} } @pages;
90 wantarray ? @flat : \@flat
91}
92
93sub catalog {
94 my ($self, $board) = @_;
95 $board = $board->{board} if ref $board;
96 my $result = $self->requestf('%s/catalog.json', $board);
97 my @pages = map { Hash::Inflator->new($_) } @$result;
98 wantarray ? @pages : \@pages
99}
100
101sub catalog_flat {
102 my @pages = shift->catalog(@_);
103 my @flat = map { @{$_->{threads}} } @pages;
104 wantarray ? @flat : \@flat
105}
106
107sub thread {
108 my ($self, $board, $threadno, $is_4chan) = @_;
109 $board = $board->{board} if ref $board;
110 $threadno = $threadno->{no} if ref $threadno;
111 $is_4chan //= (index $self->{url}, '4cdn.org') >= 0;
112 my $res_or_thread = $is_4chan ? 'thread' : 'res';
113 my $result =
114 $self->requestf('%s/%s/%s.json', $board, $res_or_thread, $threadno);
115 my @posts = map { Hash::Inflator->new($_) } @{$result->{posts}};
116 wantarray ? @posts : \@posts
117}
118
1191;
120__END__
121
122=encoding utf-8
123
124=head1 NAME
125
126WebService::Vichan - API client for 4chan and vichan-based imageboards
127
128=head1 SYNOPSIS
129
130 use WebService::Vichan qw/:all/;
131 my $chan = WebService::Vichan->new(API_4CHAN);
132
133 my @boards = $chan->boards;
134 say 'Boards on 4chan: ', join ', ', map { $_->board } @boards;
135
136 my @all_pages_of_wsg = $chan->threads('wsg');
137 my @wsg = @{$all_pages_of_wsg[0]->threads};
138 say 'IDs of threads on the first page of /wsg/: ', join ', ', map { $_->no } @wsg;
139
140 my @all_threads_of_g = $chan->threads_flat('g');
141 my @posts_in_23rd_thread = $chan->thread('g', $all_threads_of_g[22]);
142 printf "There are %d posts in the 23rd thread of /g/\n", scalar @posts_in_23rd_thread;
143 my $the_post = $posts_in_23rd_thread[1];
144 say 'HTML of the 2nd post in the 23rd thread of /g/: ', $the_post->com;
145
146=head1 DESCRIPTION
147
148This is an api client for 4chan.org and imageboards that use vichan
149(such as 8ch.net). It offers the following methods:
150
151Note: functions that ordinarily return lists will return arrayrefs if
152called in scalar context.
153
154=over
155
156=item WebService::Vichan->B<new>(I<$url>)
157
158Creates a new WebService::Vichan object with the given base URL.
159
160Two constants are exported on request by this module: C<API_4CHAN> and
161C<API_8CHAN>, which represent the base URLs for 4chan.org and 8ch.net.
162
163=item $chan->B<boards>
164
165Returns a list of available boards. These are blessed
166imageboard-dependent hashrefs which should at least have the methods
167C<board> (returning the board code as a string) and C<title>.
168
169=item $chan->B<threads>(I<$board>)
170
171Takes a board object (or a board code as a string) and returns a list
172of pages of thread OPs. Each page is a blessed hashref with methods
173C<page> (the index of the page) and C<threads> (an arrayref of thread
174OPs on that page). Each thread OP is a blessed hashref which has at
175least the methods C<no> (the thread number) and C<last_modified>.
176
177=item $chan->B<threads_flat>(I<$board>)
178
179Same as B<threads> but page information is dropped. Returns a list of
180thread OPs as described above.
181
182=item $chan->B<catalog>(I<$board>)
183
184Same as B<threads>, but much more information is returned about each
185thread OP.
186
187=item $chan->B<catalog_flat>(I<$board>)
188
189Same as B<threads_flat>, but much more information is returned about each thread OP.
190
191=item $chan->B<thread>(I<$board>, I<$threadno>, [I<$is_4chan>])
192
193Takes a board object (or a board code as a string), a thread OP object
194(or a thread number) and an optional boolean indicating whether to use
1954chan logic for the request (by default 4chan logic is used if the URL
196contains C<4cdn.org>).
197
198Returns a post object (blessed hashref) with methods as described in
199the API documentation (see links in the SEE ALSO section).
200
201=back
202
203To comply with API usage rules every request is cached for 10 seconds,
204and requests are rate-limited to one per second. If a method is called
205less than 1 second after a request has happened, it will sleep before
206issuing a second request to ensure the rate limitation is followed.
207
208=head1 SEE ALSO
209
210L<https://github.com/4chan/4chan-API>,
211L<https://github.com/vichan-devel/vichan-API/>
212
213=head1 AUTHOR
214
215Marius Gavrilescu, E<lt>marius@ieval.roE<gt>
216
217=head1 COPYRIGHT AND LICENSE
218
219Copyright (C) 2017 by Marius Gavrilescu
220
221This library is free software; you can redistribute it and/or modify
222it under the same terms as Perl itself, either Perl version 5.24.3 or,
223at your option, any later version of Perl 5 you may have available.
224
225
226=cut
This page took 0.023104 seconds and 4 git commands to generate.