File Coverage

blib/lib/Finance/Bitcoin/Feed/Site/CoinSetter.pm
Criterion Covered Total %
statement 74 84 88.1
branch 12 26 46.1
condition 6 12 50.0
subroutine 13 13 100.0
pod 1 1 100.0
total 106 136 77.9


line stmt bran cond sub pod time code
1             package Finance::Bitcoin::Feed::Site::CoinSetter;
2 1     1   22832 use strict;
  1         2  
  1         34  
3 1     1   381 use Mojo::Base 'Finance::Bitcoin::Feed::Site';
  1         6449  
  1         5  
4 1     1   653 use Mojo::UserAgent;
  1         214343  
  1         12  
5              
6             our $VERSION = '0.03';
7              
8             # Module implementation here
9             has ws_url => 'https://plug.coinsetter.com:3000/socket.io/1';
10             has 'ua';
11             has 'site' => 'COINSETTER';
12              
13             # this site need 2 handshakes
14             # 1. get session id by http GET method
15             # 2. generate a new url by adding session id to the old url
16             # 3. connect by socket id
17             sub go {
18 3     3 1 1435 my $self = shift;
19 3         15 $self->SUPER::go;
20 3         19 $self->ua(Mojo::UserAgent->new());
21 3         62 $self->debug('get handshake information');
22 3         41 my $tx = $self->ua->get($self->ws_url);
23 3 100       222 unless ($tx->success) {
24 1         54 my $err = $tx->error;
25 1         43 $self->error("Connection error of Site CoinSetter: $err->{message}");
26 1         7 $self->set_timeout;
27 1         27 return;
28             }
29              
30             # f_P7lQkhkg4JD5Xq0LCl:60:60:websocket,htmlfile,xhr-polling,jsonp-polling
31 2         72 my ($sid, $hb_timeout, $con_timeout, $transports) = split /:/, $tx->res->text;
32              
33 2         167 my $url = $self->ws_url . "/websocket/$sid";
34 2         17 $url =~ s/https/wss/;
35              
36 2         6 $self->debug('connecting...', $url);
37              
38             my $socket = $self->ua->websocket(
39             $url => sub {
40 2     2   99 my ($ua, $tx) = @_;
41 2         5 $self->debug('connected!');
42 2 100       6 unless ($tx->is_websocket) {
43 1         58 $self->error("Site BtcChina WebSocket handshake failed!");
44              
45             # set timeout;
46 1         6 $self->set_timeout;
47 1         24 return;
48             }
49 1         52 bless $tx, 'Mojo::Transaction::WebSocket::ForCoinSetterSite';
50 1         4 $tx->configure($self);
51 2         30 });
52             }
53              
54             package Mojo::Transaction::WebSocket::ForCoinSetterSite;
55 1     1   1004 use JSON;
  1         8343  
  1         15  
56 1     1   125 use Scalar::Util qw(weaken);
  1         2  
  1         56  
57              
58 1     1   4 use Mojo::Base 'Mojo::Transaction::WebSocket';
  1         1  
  1         6  
59              
60             has 'owner';
61              
62             sub configure {
63 1     1   2 my $self = shift;
64 1         2 my $owner = shift;
65 1         20 $self->owner($owner);
66 1         8 weaken($self->{owner});
67              
68             # call parse when receive text event
69             $self->on(
70             text => sub {
71 2     2   1297 my ($self, $message) = @_;
72 2         6 $self->parse($message);
73 1         8 });
74              
75             ################################################
76             # setup events
77             $self->on(
78             subscribe => sub {
79 1     1   9 my ($self, $channel) = @_;
80             $self->on(
81             'setup',
82             sub {
83 1         14 $self->send({text => qq(5:::{"name":"$channel","args":[""]})});
84 1         5 });
85 1         13 });
86 1         11 $self->emit('subscribe', 'last room');
87             $self->on(
88             last => sub {
89 1     1   53 my ($self, $data) = @_;
90 1         23 $self->owner->emit('data_out', $data->[0]{'timeStamp'}, 'BTCUSD', $data->[0]{price});
91 1         12 });
92             }
93              
94             #socketio v0.9.6
95             sub parse {
96              
97 2     2   3 my ($tx, $data) = @_;
98              
99 2         4 my @packets = ('disconnect', 'connect', 'heartbeat', 'message', 'json', 'event', 'ack', 'error', 'noop');
100              
101 2         7 my $regexp = qr/([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/;
102              
103 2         20 my @pieces = $data =~ $regexp;
104 2 50       6 return {} unless @pieces;
105 2   50     10 my $id = $pieces[1] || '';
106 2   100     8 $data = $pieces[4] || '';
107 2   50     12 my $packet = {
108             type => $packets[$pieces[0]],
109             endpoint => $pieces[3] || '',
110             };
111              
112             # whether we need to acknowledge the packet
113 2 50       4 if ($id) {
114 0         0 $packet->{id} = $id;
115 0 0       0 if ($pieces[3]) {
116 0         0 $packet->{ack} = 'data';
117             } else {
118 0         0 $packet->{ack} = 'true';
119             }
120              
121             }
122              
123             # handle different packet types
124 2 50       14 if ($packet->{type} eq 'error') {
    50          
    100          
    50          
    50          
    0          
    0          
    0          
125              
126             # need do nothing now.
127             } elsif ($packet->{type} eq 'message') {
128 0   0     0 $packet->{data} = $data || '';
129             }
130              
131             #"5:::{"name":"last","args":[{"price":367,"size":0.03,"exchangeId":"COINSETTER","timeStamp":1417382915802,"tickId":14667678802537,"volume":14.86,"volume24":102.43}]}"
132             elsif ($packet->{type} eq 'event') {
133 1         2 eval {
134 1         26 my $opts = decode_json($data);
135 1         3 $packet->{name} = $opts->{name};
136 1         4 $packet->{args} = $opts->{args};
137             };
138 1   50     3 $packet->{args} ||= [];
139              
140 1         5 $tx->emit($packet->{name}, $packet->{args});
141             } elsif ($packet->{type} eq 'json') {
142 0         0 evel {
143 0         0 $packet->{data} = decode_json($data);
144             }
145             } elsif ($packet->{type} eq 'connect') {
146 1   50     5 $packet->{qs} = $data || '';
147 1         6 $tx->emit('setup');
148             } elsif ($packet->{type} eq 'ack') {
149              
150             # nothing to do now
151             # because this site seems don't emit this packet.
152             } elsif ($packet->{type} eq 'heartbeat') {
153              
154             #send back the heartbeat
155 0           $tx->send({text => qq(2:::)});
156             } elsif ($packet->{type} eq 'disconnect') {
157 0           $tx->owner->debug('disconnected by server');
158 0           $tx->owner->set_timeout();
159             }
160             }
161              
162             1;
163              
164             __END__
165              
166             =head1 NAME
167              
168             Finance::Bitcoin::Feed::Site::CoinSetter -- the class that connect and fetch the bitcoin price data from site Coinsetter
169              
170              
171             =head1 SYNOPSIS
172              
173             use Finance::Bitcoin::Feed::Site::CoinSetter;
174             use AnyEvent;
175              
176             my $obj = Finance::Bitcoin::Feed::Site::BitStamp->new();
177             # listen on the event 'output' to get the adata
178             $obj->on('output', sub { shift; say @_ });
179             $obj->go();
180              
181             # dont forget this
182             AnyEvent->condvar->recv;
183            
184             =head1 DESCRIPTION
185              
186             Connect to site BitStamp by protocol socket.io v 0.9.6 and fetch the bitcoin price data.
187              
188             =head1 EVENTS
189              
190             This class inherits all events from L<Finance::Bitcoin::Feed::Site> and add some new ones.
191             The most important event is 'output'.
192              
193             =head2 output
194              
195             It will be emit by its parent class when print out the data. You can listen on this event to get the output.
196              
197             =head2 subscribe
198              
199             It will subscribe channel from the source site. You can subscribe more channels in the method L</configure>
200              
201             =head1 SEE ALSO
202              
203             L<Finance::Bitcoin::Feed::Site>
204              
205             L<https://www.coinsetter.com/api>
206              
207             L<Mojo::UserAgent>
208              
209             L<socket.io-parser|https://github.com/Automattic/socket.io-parser>
210              
211             =head1 AUTHOR
212              
213             Chylli C<< <chylli@binary.com> >>
214