├── dist.ini ├── .travis.yml ├── t ├── 00_compile.t ├── timeout.t └── streaming.t ├── xt ├── synopsis.t ├── pod.t ├── perlcritic.t └── podspell.t ├── .gitignore ├── Build.PL ├── cpanfile ├── eg └── track.pl ├── META.json ├── Changes ├── README.md ├── lib └── AnyEvent │ └── Twitter │ └── Stream.pm └── LICENSE /dist.ini: -------------------------------------------------------------------------------- 1 | [@Milla] 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: perl 2 | perl: 3 | - 5.16 4 | - 5.10 5 | -------------------------------------------------------------------------------- /t/00_compile.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::More tests => 1; 3 | 4 | BEGIN { use_ok 'AnyEvent::Twitter::Stream' } 5 | -------------------------------------------------------------------------------- /xt/synopsis.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | eval "use Test::Synopsis"; 3 | plan skip_all => "Test::Synopsis required" if $@; 4 | all_synopsis_ok(); 5 | -------------------------------------------------------------------------------- /xt/pod.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | eval "use Test::Pod 1.00"; 3 | plan skip_all => "Test::Pod 1.00 required for testing POD" if $@; 4 | all_pod_files_ok(); 5 | -------------------------------------------------------------------------------- /xt/perlcritic.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::More; 3 | eval q{ use Test::Perl::Critic }; 4 | plan skip_all => "Test::Perl::Critic is not installed." if $@; 5 | all_critic_ok("lib"); 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | META.yml 2 | Makefile 3 | inc/ 4 | pm_to_blib 5 | *~ 6 | MYMETA.* 7 | 8 | /AnyEvent-Twitter-Stream-* 9 | /.build 10 | /_build_params 11 | /Build 12 | !Build/ 13 | !META.json 14 | 15 | -------------------------------------------------------------------------------- /Build.PL: -------------------------------------------------------------------------------- 1 | # This Build.PL for AnyEvent-Twitter-Stream was generated by Dist::Zilla::Plugin::ModuleBuildTiny 0.015. 2 | use strict; 3 | use warnings; 4 | 5 | use 5.008001; 6 | use Module::Build::Tiny 0.034; 7 | Build_PL(); 8 | -------------------------------------------------------------------------------- /cpanfile: -------------------------------------------------------------------------------- 1 | requires 'AnyEvent'; 2 | requires 'AnyEvent::HTTP', '2.0'; 3 | requires 'JSON', '2.0'; 4 | requires 'URI'; 5 | requires 'perl', '5.008001'; 6 | 7 | on test => sub { 8 | requires 'Test::More'; 9 | requires 'Test::Requires'; 10 | requires 'Test::TCP'; 11 | }; 12 | -------------------------------------------------------------------------------- /xt/podspell.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | eval q{ use Test::Spelling }; 3 | plan skip_all => "Test::Spelling is not installed." if $@; 4 | add_stopwords(); 5 | set_spell_cmd("aspell -l en list"); 6 | all_pod_files_spelling_ok('lib'); 7 | __DATA__ 8 | Tatsuhiko 9 | Miyagawa 10 | HTTPS 11 | OAuth 12 | firehose 13 | retweet 14 | username 15 | userstream 16 | usertream 17 | -------------------------------------------------------------------------------- /eg/track.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | use strict; 3 | use AnyEvent::Twitter::Stream; 4 | 5 | if ($ENV{FIREHOSE_SERVER}) { 6 | $AnyEvent::Twitter::Stream::STREAMING_SERVER = $ENV{FIREHOSE_SERVER}; 7 | } 8 | 9 | my $done = AE::cv; 10 | 11 | my($user, $password, $method, %args) = @ARGV; 12 | 13 | binmode STDOUT, ":utf8"; 14 | 15 | my $streamer = AnyEvent::Twitter::Stream->new( 16 | username => $user, 17 | password => $password, 18 | method => $method || "sample", 19 | %args, 20 | use_compression => 1, 21 | on_tweet => sub { 22 | my $tweet = shift; 23 | print "$tweet->{user}{screen_name}: $tweet->{text}\n"; 24 | }, 25 | on_error => sub { 26 | my $error = shift; 27 | warn "ERROR: $error"; 28 | $done->send; 29 | }, 30 | on_eof => sub { 31 | $done->send; 32 | }, 33 | ); 34 | 35 | # uncomment to test undef $streamer 36 | # my $t = AE::timer 1, 0, sub { undef $streamer }; 37 | 38 | $done->recv; 39 | -------------------------------------------------------------------------------- /META.json: -------------------------------------------------------------------------------- 1 | { 2 | "abstract" : "Receive Twitter streaming API in an event loop", 3 | "author" : [ 4 | "Tatsuhiko Miyagawa " 5 | ], 6 | "dynamic_config" : 0, 7 | "generated_by" : "Dist::Milla version v1.0.16, Dist::Zilla version 6.006, CPAN::Meta::Converter version 2.150005", 8 | "license" : [ 9 | "perl_5" 10 | ], 11 | "meta-spec" : { 12 | "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", 13 | "version" : 2 14 | }, 15 | "name" : "AnyEvent-Twitter-Stream", 16 | "no_index" : { 17 | "directory" : [ 18 | "eg", 19 | "examples", 20 | "inc", 21 | "share", 22 | "t", 23 | "xt" 24 | ] 25 | }, 26 | "prereqs" : { 27 | "configure" : { 28 | "requires" : { 29 | "Module::Build::Tiny" : "0.034" 30 | } 31 | }, 32 | "develop" : { 33 | "requires" : { 34 | "Dist::Milla" : "v1.0.16", 35 | "Test::Pod" : "1.41" 36 | } 37 | }, 38 | "runtime" : { 39 | "requires" : { 40 | "AnyEvent" : "0", 41 | "AnyEvent::HTTP" : "2.0", 42 | "JSON" : "2.0", 43 | "URI" : "0", 44 | "perl" : "5.008001" 45 | } 46 | }, 47 | "test" : { 48 | "requires" : { 49 | "Test::More" : "0", 50 | "Test::Requires" : "0", 51 | "Test::TCP" : "0" 52 | } 53 | } 54 | }, 55 | "release_status" : "stable", 56 | "resources" : { 57 | "bugtracker" : { 58 | "web" : "https://github.com/miyagawa/AnyEvent-Twitter-Stream/issues" 59 | }, 60 | "homepage" : "https://github.com/miyagawa/AnyEvent-Twitter-Stream", 61 | "repository" : { 62 | "type" : "git", 63 | "url" : "https://github.com/miyagawa/AnyEvent-Twitter-Stream.git", 64 | "web" : "https://github.com/miyagawa/AnyEvent-Twitter-Stream" 65 | } 66 | }, 67 | "version" : "0.28", 68 | "x_contributors" : [ 69 | "Chris Prather ", 70 | "Daisuke Murase ", 71 | "Dan Dascalescu ", 72 | "Dexter Tad-y ", 73 | "franck cuny ", 74 | "hidekiy ", 75 | "Marc Mims ", 76 | "Mark Norman Francis ", 77 | "Masayuki Matsuki ", 78 | "megamic ", 79 | "punytan " 80 | ], 81 | "x_serialization_backend" : "Cpanel::JSON::XS version 3.0217" 82 | } 83 | 84 | -------------------------------------------------------------------------------- /t/timeout.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use AnyEvent::Twitter::Stream; 4 | use AnyEvent::Util qw(guard); 5 | use Data::Dumper; 6 | use JSON; 7 | use Test::More; 8 | use Test::TCP; 9 | use Test::Requires qw(Plack::Builder Plack::Handler::Twiggy Try::Tiny); 10 | use Test::Requires { 'Plack::Request' => '0.99' }; 11 | 12 | my %pattern = ( 13 | wait_0_0_0 => 3, 14 | wait_5_0_0 => 0, 15 | wait_0_5_0 => 0, 16 | wait_0_0_5 => 1, 17 | ); 18 | 19 | test_tcp( 20 | client => sub { 21 | my $port = shift; 22 | 23 | $AnyEvent::Twitter::Stream::STREAMING_SERVER = "localhost:$port"; 24 | $AnyEvent::Twitter::Stream::PROTOCOL = 'http'; # real world API uses https 25 | 26 | foreach my $w (keys %pattern) { 27 | my $destroyed; 28 | my $received = 0; 29 | 30 | { 31 | my $done = AE::cv; 32 | my $streamer = AnyEvent::Twitter::Stream->new( 33 | username => 'test', 34 | password => 's3cr3t', 35 | method => 'filter', 36 | track => $w, 37 | timeout => 2, 38 | on_tweet => sub { 39 | my $tweet = shift; 40 | $done->send, return if $tweet->{count} > 2; 41 | 42 | note(Dumper $tweet); 43 | $received++; 44 | }, 45 | on_error => sub { 46 | my $msg = $_[2] || $_[0]; 47 | note("on_error: $msg"); 48 | $done->send; 49 | } 50 | ); 51 | $streamer->{_guard_for_testing} = guard { $destroyed = 1 }; 52 | 53 | $done->recv; 54 | } 55 | 56 | is($destroyed, 1, "$w: destroyed"); 57 | is($received, $pattern{$w}, "received"); 58 | } 59 | }, 60 | server => sub { 61 | my $port = shift; 62 | 63 | run_streaming_server($port); 64 | }, 65 | ); 66 | 67 | done_testing(); 68 | 69 | 70 | sub run_streaming_server { 71 | my $port = shift; 72 | 73 | my $streaming = sub { 74 | my $env = shift; 75 | my $req = Plack::Request->new($env); 76 | 77 | my $track = $req->param('track'); 78 | my (undef, $wait_a, $wait_b, $wait_c) = split(/_/, $track); 79 | 80 | return sub { 81 | my $respond = shift; 82 | 83 | my $count = 0; 84 | my $writer; 85 | 86 | my $send_tweet = sub { 87 | my ($tweet) = @_; 88 | 89 | try { 90 | $writer->write(encode_json({count => $count++, rand => rand}) . "\x0D\x0A"); 91 | } catch { 92 | note($_); 93 | }; 94 | }; 95 | 96 | my $t1; $t1 = AE::timer $wait_a, 0, sub { 97 | $writer = $respond->([200, [ 98 | 'Content-Type' => 'application/json' 99 | ]]); 100 | 101 | undef $t1; 102 | }; 103 | 104 | my $t2; $t2 = AE::timer $wait_a + $wait_b, 0, sub { 105 | $send_tweet->(); 106 | 107 | undef $t2; 108 | }; 109 | 110 | my $t3; $t3 = AE::timer $wait_a + $wait_b + $wait_c, 0.5, sub { 111 | $send_tweet->(); 112 | 113 | $t3; 114 | }; 115 | }; 116 | }; 117 | 118 | 119 | my $app = builder { 120 | enable 'Auth::Basic', realm => 'Firehose', authenticator => sub { 121 | my ($user, $pass) = @_; 122 | 123 | return $user eq 'test' && $pass eq 's3cr3t'; 124 | }; 125 | mount '/1.1/' => $streaming; 126 | }; 127 | 128 | my $server = Plack::Handler::Twiggy->new( 129 | host => '127.0.0.1', 130 | port => $port, 131 | )->run($app); 132 | } 133 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | Revision history for Perl extension AnyEvent::Twitter::Stream 2 | 3 | {{$NEXT}} 4 | 5 | 0.28 2016-07-29 18:02:28 PDT 6 | - Make it work on perl 5.24 7 | 8 | 0.27 2014-04-18 14:08:51 JST 9 | - Fixed Chunked handling for long JSON strings (dexterbt1) 10 | 11 | 0.26 2013-06-10 14:52:56 PDT 12 | - Fix dependencies 13 | 14 | 0.25 2013-05-20 18:02:46 PDT 15 | - Encode URI parameters with UTF-8 (Yapo, Songmu) 16 | 17 | 0.24 2013-05-13 16:26:48 PDT 18 | - Make it non-trial 19 | - Convert to Milla 20 | 21 | 0.23 Sat Mar 9 23:33:48 PST 2013 22 | - Added on_direct_message support (semifor) 23 | - Fixed compression (perigrin) 24 | - Support 1.1 API and Site Streams (semifor) 25 | 26 | 0.22 Tue Oct 4 23:52:17 PDT 2011 27 | - Streaming is now SSL only 28 | 29 | 0.21 Tue May 17 09:54:03 PDT 2011 30 | - Same as 0.20_1 31 | 32 | 0.20_1 Wed Apr 13 17:14:25 PDT 2011 33 | - Workaround with AE::HTTP 2.0 that forces HTTP/1.1 which makes the Twitter response 34 | transfered in chunked encoding (hidekiy) 35 | 36 | 0.20 Fri Oct 1 01:10:51 PDT 2010 37 | - UserStreams is now live. Updated the host name and protocol to use HTTPS (norm) 38 | 39 | 0.19 Fri Sep 17 14:44:01 PDT 2010 40 | - Fixed on_connect callback so it only gets fired in the real connection, not 41 | pseudo callback for 4xx/5xx responses 42 | 43 | 0.18 Fri Sep 17 14:26:06 PDT 2010 44 | - Added on_connect callback 45 | 46 | 0.17 Mon Sep 13 16:56:59 PDT 2010 47 | - Remove artificial on_eof callback when $client gets undef (hachi, miyagawa) 48 | 49 | 0.16 Fri Aug 20 16:58:14 PDT 2010 50 | - Support UserStreams (franck) 51 | - SSL support (franck) 52 | 53 | 0.15 Wed Aug 18 00:48:16 PDT 2010 54 | - Updated tests to use Twiggy rather than P::S::AE (hidekiy) 55 | 56 | 0.14 Mon Aug 16 15:08:41 PDT 2010 57 | - Added OAuth support (franck) 58 | - Added on_delete event (franck) 59 | 60 | 0.13 Thu Apr 1 14:00:35 PDT 2010 61 | - Fixed an error when HTTP connection is disconnected 62 | http://rt.cpan.org/Public/Bug/Display.html?id=55798 63 | 64 | 0.12 Tue Mar 23 12:55:01 PDT 2010 65 | - Support new 'locations' parameter for 'filter' method (requested by M. Edward (Ed) Borasky) 66 | - Support multiple post parameters for 'filter'. 67 | ... I thought this has been there for a while but it wasn't. 68 | - Support partner-only endpoints: links and retweet 69 | 70 | 0.11 Mon Mar 1 17:12:37 PST 2010 71 | - Added 'no_decode_json' option that passes the undecoded raw JSON to the callback 72 | 73 | 0.10 Sat Feb 6 17:55:28 PST 2010 74 | - Fixed a possible memory leak (hidekiy) 75 | 76 | 0.09 Mon Dec 7 16:45:06 PST 2009 77 | - Added on_keepalive and timeout options (Marc Mims) 78 | 79 | 0.08 Thu Sep 17 19:27:29 JST 2009 80 | - Added on_error to the handle (thanks to typester) 81 | 82 | 0.07 Mon Aug 31 04:06:18 PDT 2009 83 | - Fixed POD code to match with 0.05 API 84 | 85 | 0.06 Thu Aug 27 10:08:07 PDT 2009 86 | - filter track/follow params should be POST not GET (megamic) 87 | 88 | 0.05 Wed Aug 26 16:31:52 PDT 2009 89 | - Unbreak the refcount stuff introduced in 0.03 90 | - BACKWARD INCOMPATIBLE: method names should now be either 'firehose', 'sample' or 'filter' 91 | per Twitter API update. 92 | http://groups.google.com/group/twitter-development-talk/browse_frm/thread/44bd32155dbf2c16 93 | 94 | 0.04 Wed Aug 26 01:59:30 PDT 2009 95 | - test_requires Test::TCP 96 | 97 | 0.03 Tue Aug 25 19:08:29 PDT 2009 98 | - Fixed a bug where undef $client doesn't really disconnect 99 | (Thanks to Daisuke Murase) 100 | 101 | 0.02 Sat Jul 18 18:20:18 PDT 2009 102 | - Ignore newline when kee-alive empty line is coming when no tweets are there 103 | (Thanks to Masayoshi Sekimura) 104 | 105 | 0.01 Mon Jul 13 13:46:08 2009 106 | - original version 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NAME 2 | 3 | AnyEvent::Twitter::Stream - Receive Twitter streaming API in an event loop 4 | 5 | # SYNOPSIS 6 | 7 | use AnyEvent::Twitter::Stream; 8 | 9 | my $done = AE::cv; 10 | 11 | # receive updates from @following_ids 12 | my $listener = AnyEvent::Twitter::Stream->new( 13 | username => $user, 14 | password => $password, 15 | method => "filter", # "firehose" for everything, "sample" for sample timeline 16 | follow => join(",", @following_ids), # numeric IDs 17 | on_tweet => sub { 18 | my $tweet = shift; 19 | warn "$tweet->{user}{screen_name}: $tweet->{text}\n"; 20 | }, 21 | on_keepalive => sub { 22 | warn "ping\n"; 23 | }, 24 | on_delete => sub { 25 | my ($tweet_id, $user_id) = @_; # callback executed when twitter send a delete notification 26 | ... 27 | }, 28 | timeout => 45, 29 | ); 30 | 31 | # track keywords 32 | my $guard = AnyEvent::Twitter::Stream->new( 33 | username => $user, 34 | password => $password, 35 | method => "filter", 36 | track => "Perl,Test,Music", 37 | on_tweet => sub { }, 38 | ); 39 | 40 | # to use OAuth authentication 41 | my $listener = AnyEvent::Twitter::Stream->new( 42 | consumer_key => $consumer_key, 43 | consumer_secret => $consumer_secret, 44 | token => $token, 45 | token_secret => $token_secret, 46 | method => "filter", 47 | track => "...", 48 | on_tweet => sub { ... }, 49 | ); 50 | 51 | $done->recv; 52 | 53 | # DESCRIPTION 54 | 55 | AnyEvent::Twitter::Stream is an AnyEvent user to receive Twitter streaming 56 | API, available at [http://dev.twitter.com/pages/streaming\_api](http://dev.twitter.com/pages/streaming_api) and 57 | [http://dev.twitter.com/pages/user\_streams](http://dev.twitter.com/pages/user_streams). 58 | 59 | See ["track.pl" in eg](https://metacpan.org/pod/eg#track.pl) for more client code example. 60 | 61 | # METHODS 62 | 63 | ## my $streamer = AnyEvent::Twitter::Stream->new(%args); 64 | 65 | - **username** **password** 66 | 67 | These arguments are used for basic authentication. 68 | 69 | - **consumer\_key** **consumer\_secret** **token** **token\_secret** 70 | 71 | If you want to use the OAuth authentication mechanism, you need to set these arguments 72 | 73 | - **method** 74 | 75 | The name of the method you want to use on the stream. Currently, any one of : 76 | 77 | - **firehose** 78 | - **sample** 79 | - **userstream** 80 | 81 | To use this method, you need to use the OAuth mechanism. 82 | 83 | - **filter** 84 | 85 | With this method you can specify what you want to filter amongst **track**, **follow** and **locations**. 86 | 87 | See [https://dev.twitter.com/docs/api/1.1/post/statuses/filter](https://dev.twitter.com/docs/api/1.1/post/statuses/filter) for the details of the parameters. 88 | 89 | - **api\_url** 90 | 91 | Pass this to override the default URL for the API endpoint. 92 | 93 | - **request\_method** 94 | 95 | Pass this to override the default HTTP request method. 96 | 97 | - **timeout** 98 | 99 | Set the timeout value. 100 | 101 | - **on\_connect** 102 | 103 | Callback to execute when a stream is connected. 104 | 105 | - **on\_tweet** 106 | 107 | Callback to execute when a new tweet is received. The argument is the tweet, a hashref documented at 108 | [https://dev.twitter.com/docs/api/1/get/statuses/show/%3Aid](https://dev.twitter.com/docs/api/1/get/statuses/show/%3Aid). 109 | 110 | - **on\_error** 111 | - **on\_eof** 112 | - **on\_keepalive** 113 | - **on\_delete** 114 | 115 | Callback to execute when the stream send a delete notification. 116 | 117 | - **on\_friends** 118 | 119 | **Only with the usertream method**. Callback to execute when the stream send a list of friends. 120 | 121 | - **on\_direct\_message** 122 | 123 | **Only with the usertream method**. Callback to execute when a direct message is received in the stream. 124 | 125 | - **on\_event** 126 | 127 | **Only with the userstream method**. Callback to execute when the stream send an event notification (follow, ...). 128 | 129 | - **additional agruments** 130 | 131 | Any additional arguments are assumed to be parameters to the underlying API method and are passed to Twitter. 132 | 133 | # NOTES 134 | 135 | To use the **userstream** method, Twitter recommend using the HTTPS protocol. For this, you need to set the **ANYEVENT\_TWITTER\_STREAM\_SSL** environment variable, and install the [Net::SSLeay](https://metacpan.org/pod/Net::SSLeay) module. 136 | 137 | # AUTHOR 138 | 139 | Tatsuhiko Miyagawa 140 | 141 | # LICENSE 142 | 143 | This library is free software; you can redistribute it and/or modify 144 | it under the same terms as Perl itself. 145 | 146 | # SEE ALSO 147 | 148 | [AnyEvent::Twitter](https://metacpan.org/pod/AnyEvent::Twitter), [Net::Twitter::Stream](https://metacpan.org/pod/Net::Twitter::Stream) 149 | -------------------------------------------------------------------------------- /t/streaming.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use AnyEvent; 4 | use AnyEvent::Twitter::Stream; 5 | use AnyEvent::Util qw(guard); 6 | use Data::Dumper; 7 | use JSON; 8 | use Test::More; 9 | use Test::TCP; 10 | use Test::Requires qw(Plack::Builder Plack::Handler::Twiggy Try::Tiny); 11 | use Test::Requires { 'Plack::Request' => '0.99' }; 12 | 13 | my @pattern = ( 14 | { 15 | method => 'sample', 16 | mount => 'stream', 17 | path => '/1.1/statuses/sample.json', 18 | option => {}, 19 | }, 20 | { 21 | method => 'firehose', 22 | mount => 'stream', 23 | path => '/1.1/statuses/firehose.json', 24 | option => {}, 25 | }, 26 | { 27 | method => 'filter', 28 | mount => 'stream', 29 | path => '/1.1/statuses/filter.json', 30 | option => {track => 'hogehoge'}, 31 | }, 32 | { 33 | method => 'filter', 34 | path => '/1.1/statuses/filter.json', 35 | option => {follow => '123123'}, 36 | }, 37 | { 38 | method => 'userstream', 39 | path => '/1.1/user.json', 40 | option => {}, 41 | }, 42 | ); 43 | 44 | foreach my $enable_chunked (0, 1) { 45 | test_tcp( 46 | client => sub { 47 | my $port = shift; 48 | 49 | local $AnyEvent::Twitter::Stream::STREAMING_SERVER = "127.0.0.1:$port"; 50 | local $AnyEvent::Twitter::Stream::USERSTREAM_SERVER = "127.0.0.1:$port"; 51 | local $AnyEvent::Twitter::Stream::US_PROTOCOL = "http"; 52 | local $AnyEvent::Twitter::Stream::PROTOCOL = 'http'; # real world API uses https 53 | 54 | foreach my $item (@pattern) { 55 | my $destroyed; 56 | my $received = 0; 57 | my $count_max = 5; 58 | my ($deleted, $event) = (0, 0); 59 | 60 | note("try $item->{method}"); 61 | 62 | { 63 | my $done = AE::cv; 64 | my $streamer = AnyEvent::Twitter::Stream->new( 65 | username => 'test', 66 | password => 's3cr3t', 67 | method => $item->{method}, 68 | timeout => 2, 69 | on_tweet => sub { 70 | my $tweet = shift; 71 | 72 | if ($tweet->{hello}) { 73 | note(Dumper $tweet); 74 | is($tweet->{user}, 'test'); 75 | is($tweet->{path}, $item->{path}); 76 | is_deeply($tweet->{param}, $item->{option}); 77 | 78 | if (%{$item->{option}}) { 79 | is($tweet->{request_method}, 'POST'); 80 | } else { 81 | is($tweet->{request_method}, 'GET'); 82 | } 83 | } else { 84 | $done->send, return if $tweet->{count} > $count_max; 85 | } 86 | 87 | $received++; 88 | }, 89 | on_delete => sub { 90 | my ($tweet_id, $user_id) = @_; 91 | $deleted++; 92 | $received++; 93 | }, 94 | on_friends => sub { 95 | my $friends = shift; 96 | is_deeply($friends, [qw/1 2 3/]); 97 | }, 98 | on_event => sub { 99 | $event++; 100 | $done->send; 101 | }, 102 | on_error => sub { 103 | my $msg = $_[2] || $_[0]; 104 | fail("on_error: $msg"); 105 | $done->send; 106 | }, 107 | %{$item->{option}}, 108 | ); 109 | $streamer->{_guard_for_testing} = guard { $destroyed = 1 }; 110 | 111 | $done->recv; 112 | } 113 | 114 | if ($item->{method} eq 'sample') { 115 | is $deleted, 1, 'deleted one tweet'; 116 | } else { 117 | is $deleted, 0, 'deleted no tweet'; 118 | } 119 | 120 | if ($item->{method} =~ /userstream|sitestream/) { 121 | is $event, 1, 'got one event'; 122 | } else { 123 | is $event, 0, 'got no event'; 124 | is($received, $count_max + 1, "received"); 125 | } 126 | 127 | is $destroyed, 1, 'destroyed'; 128 | } 129 | }, 130 | server => sub { 131 | my $port = shift; 132 | 133 | run_streaming_server($port, $enable_chunked); 134 | }, 135 | ); 136 | } 137 | 138 | done_testing(); 139 | 140 | sub run_streaming_server { 141 | my ($port, $enable_chunked) = @_; 142 | 143 | my $streaming = sub { 144 | my $env = shift; 145 | my $req = Plack::Request->new($env); 146 | 147 | return sub { 148 | my $respond = shift; 149 | 150 | my $writer = $respond->([200, [ 151 | 'Content-Type' => 'application/json', 152 | 'Server' => 'Jetty(6.1.17)', 153 | ]]); 154 | 155 | $writer->write(encode_json({ 156 | hello => 1, 157 | path => $req->path, 158 | request_method => $req->method, 159 | user => $env->{REMOTE_USER}, 160 | param => $req->parameters->mixed, 161 | }) . "\x0D\x0A"); 162 | 163 | my $count = 1; 164 | my $t; $t = AE::timer(0, 0.2, sub { 165 | try { 166 | $writer->write(encode_json({ 167 | body => 'x' x 500, 168 | count => $count++, 169 | }) . "\x0D\x0A"); 170 | } catch { 171 | undef $t; 172 | }; 173 | if ($req->path =~ /sample/ && $count == 2) { 174 | try { 175 | $writer->write(encode_json({ 176 | delete => {status => {id => 1, user_id => 1}}, 177 | count => $count++, 178 | }) . "\x0D\x0A"); 179 | } catch { 180 | undef $t; 181 | }; 182 | } 183 | }); 184 | }; 185 | }; 186 | 187 | my $user_stream = sub { 188 | my $env = shift; 189 | my $req = Plack::Request->new($env); 190 | 191 | return sub { 192 | my $respond = shift; 193 | 194 | my $writer = $respond->([200, [ 195 | 'Content-Type' => 'application/json', 196 | 'Server' => 'Jetty(6.1.17)', 197 | ]]); 198 | $writer->write(encode_json({ 199 | friends => [qw/1 2 3/], 200 | }) . "\x0D\x0A"); 201 | 202 | my $t; $t = AE::timer(0, 0.2, sub { 203 | try { 204 | $writer->write(encode_json({ 205 | event => {foo => 'bar'}, 206 | }) . "\x0D\x0A"); 207 | }catch{ 208 | undef $t; 209 | }; 210 | }); 211 | }; 212 | }; 213 | 214 | my $app = builder { 215 | enable 'Auth::Basic', realm => 'Firehose', authenticator => sub { 216 | my ($user, $pass) = @_; 217 | 218 | return $user eq 'test' && $pass eq 's3cr3t'; 219 | }; 220 | enable 'Chunked' if $enable_chunked; 221 | 222 | mount '/1.1/statuses/' => $streaming; 223 | mount '/' => $user_stream; 224 | }; 225 | 226 | my $server = Plack::Handler::Twiggy->new( 227 | host => '127.0.0.1', 228 | port => $port, 229 | )->run($app); 230 | } 231 | -------------------------------------------------------------------------------- /lib/AnyEvent/Twitter/Stream.pm: -------------------------------------------------------------------------------- 1 | package AnyEvent::Twitter::Stream; 2 | 3 | use strict; 4 | use 5.008_001; 5 | our $VERSION = '0.28'; 6 | 7 | use AnyEvent; 8 | use AnyEvent::HTTP; 9 | use AnyEvent::Util; 10 | use MIME::Base64; 11 | use URI; 12 | use URI::Escape; 13 | use Carp; 14 | use Compress::Raw::Zlib; 15 | 16 | our $STREAMING_SERVER = 'stream.twitter.com'; 17 | our $USERSTREAM_SERVER = 'userstream.twitter.com'; 18 | our $SITESTREAM_SERVER = 'sitestream.twitter.com'; 19 | our $PROTOCOL = 'https'; 20 | our $US_PROTOCOL = 'https'; # for testing 21 | 22 | my %methods = ( 23 | filter => [ POST => sub { "$PROTOCOL://$STREAMING_SERVER/1.1/statuses/filter.json" } ], 24 | sample => [ GET => sub { "$PROTOCOL://$STREAMING_SERVER/1.1/statuses/sample.json" } ], 25 | firehose => [ GET => sub { "$PROTOCOL://$STREAMING_SERVER/1.1/statuses/firehose.json" } ], 26 | userstream => [ GET => sub { "$US_PROTOCOL://$USERSTREAM_SERVER/1.1/user.json" } ], 27 | sitestream => [ GET => sub { "$PROTOCOL://$SITESTREAM_SERVER/1.1/site.json" } ], 28 | 29 | # DEPRECATED 30 | links => [ GET => sub { "$PROTOCOL://$STREAMING_SERVER/1/statuses/links.json" } ], 31 | retweet => [ GET => sub { "$PROTOCOL://$STREAMING_SERVER/1/statuses/retweet.json" } ], 32 | ); 33 | 34 | sub new { 35 | my $class = shift; 36 | my %args = @_; 37 | 38 | my $username = delete $args{username}; 39 | my $password = delete $args{password}; 40 | my $consumer_key = delete $args{consumer_key}; 41 | my $consumer_secret = delete $args{consumer_secret}; 42 | my $token = delete $args{token}; 43 | my $token_secret = delete $args{token_secret}; 44 | my $method = delete $args{method}; 45 | my $on_connect = delete $args{on_connect} || sub { }; 46 | my $on_tweet = delete $args{on_tweet}; 47 | my $on_error = delete $args{on_error} || sub { die @_ }; 48 | my $on_eof = delete $args{on_eof} || sub { }; 49 | my $on_keepalive = delete $args{on_keepalive} || sub { }; 50 | my $on_delete = delete $args{on_delete}; 51 | my $on_friends = delete $args{on_friends}; 52 | my $on_direct_message = delete $args{on_direct_message}; 53 | my $on_event = delete $args{on_event}; 54 | my $timeout = delete $args{timeout}; 55 | 56 | my $decode_json; 57 | unless (delete $args{no_decode_json}) { 58 | require JSON; 59 | $decode_json = 1; 60 | } 61 | 62 | my ($zlib, $_zstatus); 63 | if (delete $args{use_compression}){ 64 | ($zlib, $_zstatus) = Compress::Raw::Zlib::Inflate->new( 65 | -LimitOutput => 1, 66 | -AppendOutput => 1, 67 | -WindowBits => WANT_GZIP_OR_ZLIB, 68 | ); 69 | die "Can't make inflator: $_zstatus" unless $zlib; 70 | } 71 | 72 | unless ($methods{$method} || exists $args{api_url} ) { 73 | $on_error->("Method $method not available."); 74 | return; 75 | } 76 | 77 | my $uri = URI->new(delete $args{api_url} || $methods{$method}[1]()); 78 | 79 | my $request_body; 80 | my $request_method = delete $args{request_method} || $methods{$method}[0] || 'GET'; 81 | if ( $request_method eq 'POST' ) { 82 | $request_body = join '&', map "$_=" . URI::Escape::uri_escape_utf8($args{$_}), keys %args; 83 | }else{ 84 | $uri->query_form(%args); 85 | } 86 | 87 | my $auth; 88 | if ($consumer_key) { 89 | eval {require Net::OAuth;}; 90 | die $@ if $@; 91 | 92 | my $request = Net::OAuth->request('protected resource')->new( 93 | version => '1.0', 94 | consumer_key => $consumer_key, 95 | consumer_secret => $consumer_secret, 96 | token => $token, 97 | token_secret => $token_secret, 98 | request_method => $request_method, 99 | signature_method => 'HMAC-SHA1', 100 | timestamp => time, 101 | nonce => MIME::Base64::encode( time . $$ . rand ), 102 | request_url => $uri, 103 | $request_method eq 'POST' ? (extra_params => \%args) : (), 104 | ); 105 | $request->sign; 106 | $auth = $request->to_authorization_header; 107 | }else{ 108 | $auth = "Basic ".MIME::Base64::encode("$username:$password", ''); 109 | } 110 | 111 | my $self = bless {}, $class; 112 | 113 | { 114 | Scalar::Util::weaken(my $self = $self); 115 | 116 | my $set_timeout = $timeout 117 | ? sub { $self->{timeout} = AE::timer($timeout, 0, sub { $on_error->('timeout') }) } 118 | : sub {}; 119 | 120 | my $on_json_message = sub { 121 | my ($json) = @_; 122 | 123 | # Twitter stream returns "\x0a\x0d\x0a" if there's no matched tweets in ~30s. 124 | $set_timeout->(); 125 | if ($json !~ /^\s*$/) { 126 | my $tweet = $decode_json ? JSON::decode_json($json) : $json; 127 | if ($on_delete && $tweet->{delete} && $tweet->{delete}->{status}) { 128 | $on_delete->($tweet->{delete}->{status}->{id}, $tweet->{delete}->{status}->{user_id}); 129 | }elsif($on_friends && $tweet->{friends}) { 130 | $on_friends->($tweet->{friends}); 131 | }elsif($on_direct_message && $tweet->{direct_message}) { 132 | $on_direct_message->($tweet->{direct_message}); 133 | }elsif($on_event && $tweet->{event}) { 134 | $on_event->($tweet); 135 | }else{ 136 | $on_tweet->($tweet); 137 | } 138 | } 139 | else { 140 | $on_keepalive->(); 141 | } 142 | }; 143 | 144 | $set_timeout->(); 145 | 146 | $self->{connection_guard} = http_request($request_method, $uri, 147 | headers => { 148 | Accept => '*/*', 149 | ( defined $zlib ? ('Accept-Encoding' => 'deflate, gzip') : ()), 150 | Authorization => $auth, 151 | ($request_method eq 'POST' 152 | ? ('Content-Type' => 'application/x-www-form-urlencoded') 153 | : () 154 | ), 155 | }, 156 | body => $request_body, 157 | on_header => sub { 158 | my($headers) = @_; 159 | if ($headers->{Status} ne '200') { 160 | $on_error->("$headers->{Status}: $headers->{Reason}"); 161 | return; 162 | } 163 | return 1; 164 | }, 165 | want_body_handle => 1, # for some reason on_body => sub {} doesn't work :/ 166 | sub { 167 | my ($handle, $headers) = @_; 168 | 169 | return unless $handle; 170 | my $input; 171 | my $chunk_reader; 172 | $chunk_reader = sub { 173 | my ($handle, $line) = @_; 174 | 175 | $line =~ /^([0-9a-fA-F]+)/ or die 'bad chunk (incorrect length)'; 176 | my $len = hex $1; 177 | my $chunk_part_reader; 178 | $chunk_part_reader = sub { 179 | my ($handle, $chunk_raw) = @_; 180 | my $chunk = $chunk_raw; 181 | $chunk =~ s/\r\n$//; 182 | $input .= $chunk; 183 | unless ($headers->{'content-encoding'}) { 184 | while ($input =~ s/^(.*?)\r\n//) { 185 | my ($json_raw) = $1; 186 | $on_json_message->($json_raw); 187 | } 188 | } elsif ($headers->{'content-encoding'} =~ 'deflate|gzip') { 189 | my ($message); 190 | do { 191 | $_zstatus = $zlib->inflate(\$input, \$message); 192 | return unless $_zstatus == Z_OK || $_zstatus == Z_BUF_ERROR; 193 | } while ( $_zstatus == Z_OK && length $input ); 194 | $on_json_message->($message); 195 | } else { 196 | die "Don't know how to decode $headers->{'content-encoding'}" 197 | } 198 | $handle->push_read(line => $chunk_reader); 199 | }; # chunk_part_reader 200 | $handle->push_read(chunk => $len + 2, $chunk_part_reader); 201 | }; 202 | 203 | my $line_reader = sub { 204 | my ($handle, $line) = @_; 205 | 206 | $on_json_message->($line); 207 | }; 208 | 209 | $handle->on_error(sub { 210 | undef $handle; 211 | $on_error->($_[2]); 212 | }); 213 | $handle->on_eof(sub { 214 | undef $handle; 215 | $on_eof->(@_); 216 | }); 217 | 218 | if (($headers->{'transfer-encoding'} || '') =~ /\bchunked\b/i) { 219 | $handle->on_read(sub { 220 | my ($handle) = @_; 221 | $handle->push_read(line => $chunk_reader); 222 | }); 223 | } else { 224 | $handle->on_read(sub { 225 | my ($handle) = @_; 226 | $handle->push_read(line => $line_reader); 227 | }); 228 | } 229 | 230 | $self->{guard} = AnyEvent::Util::guard { 231 | $handle->destroy if $handle; 232 | }; 233 | 234 | $on_connect->(); 235 | } 236 | ); 237 | } 238 | 239 | return $self; 240 | } 241 | 242 | 1; 243 | __END__ 244 | 245 | =encoding utf-8 246 | 247 | =for stopwords 248 | API AnyEvent 249 | 250 | =for test_synopsis 251 | my($user, $password, @following_ids, $consumer_key, $consumer_secret, $token, $token_secret); 252 | 253 | =head1 NAME 254 | 255 | AnyEvent::Twitter::Stream - Receive Twitter streaming API in an event loop 256 | 257 | =head1 SYNOPSIS 258 | 259 | use AnyEvent::Twitter::Stream; 260 | 261 | my $done = AE::cv; 262 | 263 | # receive updates from @following_ids 264 | my $listener = AnyEvent::Twitter::Stream->new( 265 | username => $user, 266 | password => $password, 267 | method => "filter", # "firehose" for everything, "sample" for sample timeline 268 | follow => join(",", @following_ids), # numeric IDs 269 | on_tweet => sub { 270 | my $tweet = shift; 271 | warn "$tweet->{user}{screen_name}: $tweet->{text}\n"; 272 | }, 273 | on_keepalive => sub { 274 | warn "ping\n"; 275 | }, 276 | on_delete => sub { 277 | my ($tweet_id, $user_id) = @_; # callback executed when twitter send a delete notification 278 | ... 279 | }, 280 | timeout => 45, 281 | ); 282 | 283 | # track keywords 284 | my $guard = AnyEvent::Twitter::Stream->new( 285 | username => $user, 286 | password => $password, 287 | method => "filter", 288 | track => "Perl,Test,Music", 289 | on_tweet => sub { }, 290 | ); 291 | 292 | # to use OAuth authentication 293 | my $listener = AnyEvent::Twitter::Stream->new( 294 | consumer_key => $consumer_key, 295 | consumer_secret => $consumer_secret, 296 | token => $token, 297 | token_secret => $token_secret, 298 | method => "filter", 299 | track => "...", 300 | on_tweet => sub { ... }, 301 | ); 302 | 303 | $done->recv; 304 | 305 | =head1 DESCRIPTION 306 | 307 | AnyEvent::Twitter::Stream is an AnyEvent user to receive Twitter streaming 308 | API, available at L and 309 | L. 310 | 311 | See L for more client code example. 312 | 313 | =head1 METHODS 314 | 315 | =head2 my $streamer = AnyEvent::Twitter::Stream->new(%args); 316 | 317 | =over 4 318 | 319 | =item B B 320 | 321 | These arguments are used for basic authentication. 322 | 323 | =item B B B B 324 | 325 | If you want to use the OAuth authentication mechanism, you need to set these arguments 326 | 327 | =item B 328 | 329 | The name of the method you want to use on the stream. Currently, any one of : 330 | 331 | =over 2 332 | 333 | =item B 334 | 335 | =item B 336 | 337 | =item B 338 | 339 | To use this method, you need to use the OAuth mechanism. 340 | 341 | =item B 342 | 343 | With this method you can specify what you want to filter amongst B, B and B. 344 | 345 | See L for the details of the parameters. 346 | 347 | =back 348 | 349 | =item B 350 | 351 | Pass this to override the default URL for the API endpoint. 352 | 353 | =item B 354 | 355 | Pass this to override the default HTTP request method. 356 | 357 | =item B 358 | 359 | Set the timeout value. 360 | 361 | =item B 362 | 363 | Callback to execute when a stream is connected. 364 | 365 | =item B 366 | 367 | Callback to execute when a new tweet is received. The argument is the tweet, a hashref documented at 368 | L. 369 | 370 | =item B 371 | 372 | =item B 373 | 374 | =item B 375 | 376 | =item B 377 | 378 | Callback to execute when the stream send a delete notification. 379 | 380 | =item B 381 | 382 | B. Callback to execute when the stream send a list of friends. 383 | 384 | =item B 385 | 386 | B. Callback to execute when a direct message is received in the stream. 387 | 388 | =item B 389 | 390 | B. Callback to execute when the stream send an event notification (follow, ...). 391 | 392 | =item B 393 | 394 | Any additional arguments are assumed to be parameters to the underlying API method and are passed to Twitter. 395 | 396 | =back 397 | 398 | =head1 NOTES 399 | 400 | To use the B method, Twitter recommend using the HTTPS protocol. For this, you need to set the B environment variable, and install the L module. 401 | 402 | =head1 AUTHOR 403 | 404 | Tatsuhiko Miyagawa Emiyagawa@bulknews.netE 405 | 406 | =head1 LICENSE 407 | 408 | This library is free software; you can redistribute it and/or modify 409 | it under the same terms as Perl itself. 410 | 411 | =head1 SEE ALSO 412 | 413 | L, L 414 | 415 | =cut 416 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is copyright (c) 2016 by Tatsuhiko Miyagawa . 2 | 3 | This is free software; you can redistribute it and/or modify it under 4 | the same terms as the Perl 5 programming language system itself. 5 | 6 | Terms of the Perl programming language system itself 7 | 8 | a) the GNU General Public License as published by the Free 9 | Software Foundation; either version 1, or (at your option) any 10 | later version, or 11 | b) the "Artistic License" 12 | 13 | --- The GNU General Public License, Version 1, February 1989 --- 14 | 15 | This software is Copyright (c) 2016 by Tatsuhiko Miyagawa . 16 | 17 | This is free software, licensed under: 18 | 19 | The GNU General Public License, Version 1, February 1989 20 | 21 | GNU GENERAL PUBLIC LICENSE 22 | Version 1, February 1989 23 | 24 | Copyright (C) 1989 Free Software Foundation, Inc. 25 | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 26 | 27 | Everyone is permitted to copy and distribute verbatim copies 28 | of this license document, but changing it is not allowed. 29 | 30 | Preamble 31 | 32 | The license agreements of most software companies try to keep users 33 | at the mercy of those companies. By contrast, our General Public 34 | License is intended to guarantee your freedom to share and change free 35 | software--to make sure the software is free for all its users. The 36 | General Public License applies to the Free Software Foundation's 37 | software and to any other program whose authors commit to using it. 38 | You can use it for your programs, too. 39 | 40 | When we speak of free software, we are referring to freedom, not 41 | price. Specifically, the General Public License is designed to make 42 | sure that you have the freedom to give away or sell copies of free 43 | software, that you receive source code or can get it if you want it, 44 | that you can change the software or use pieces of it in new free 45 | programs; and that you know you can do these things. 46 | 47 | To protect your rights, we need to make restrictions that forbid 48 | anyone to deny you these rights or to ask you to surrender the rights. 49 | These restrictions translate to certain responsibilities for you if you 50 | distribute copies of the software, or if you modify it. 51 | 52 | For example, if you distribute copies of a such a program, whether 53 | gratis or for a fee, you must give the recipients all the rights that 54 | you have. You must make sure that they, too, receive or can get the 55 | source code. And you must tell them their rights. 56 | 57 | We protect your rights with two steps: (1) copyright the software, and 58 | (2) offer you this license which gives you legal permission to copy, 59 | distribute and/or modify the software. 60 | 61 | Also, for each author's protection and ours, we want to make certain 62 | that everyone understands that there is no warranty for this free 63 | software. If the software is modified by someone else and passed on, we 64 | want its recipients to know that what they have is not the original, so 65 | that any problems introduced by others will not reflect on the original 66 | authors' reputations. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | GNU GENERAL PUBLIC LICENSE 72 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 73 | 74 | 0. This License Agreement applies to any program or other work which 75 | contains a notice placed by the copyright holder saying it may be 76 | distributed under the terms of this General Public License. The 77 | "Program", below, refers to any such program or work, and a "work based 78 | on the Program" means either the Program or any work containing the 79 | Program or a portion of it, either verbatim or with modifications. Each 80 | licensee is addressed as "you". 81 | 82 | 1. You may copy and distribute verbatim copies of the Program's source 83 | code as you receive it, in any medium, provided that you conspicuously and 84 | appropriately publish on each copy an appropriate copyright notice and 85 | disclaimer of warranty; keep intact all the notices that refer to this 86 | General Public License and to the absence of any warranty; and give any 87 | other recipients of the Program a copy of this General Public License 88 | along with the Program. You may charge a fee for the physical act of 89 | transferring a copy. 90 | 91 | 2. You may modify your copy or copies of the Program or any portion of 92 | it, and copy and distribute such modifications under the terms of Paragraph 93 | 1 above, provided that you also do the following: 94 | 95 | a) cause the modified files to carry prominent notices stating that 96 | you changed the files and the date of any change; and 97 | 98 | b) cause the whole of any work that you distribute or publish, that 99 | in whole or in part contains the Program or any part thereof, either 100 | with or without modifications, to be licensed at no charge to all 101 | third parties under the terms of this General Public License (except 102 | that you may choose to grant warranty protection to some or all 103 | third parties, at your option). 104 | 105 | c) If the modified program normally reads commands interactively when 106 | run, you must cause it, when started running for such interactive use 107 | in the simplest and most usual way, to print or display an 108 | announcement including an appropriate copyright notice and a notice 109 | that there is no warranty (or else, saying that you provide a 110 | warranty) and that users may redistribute the program under these 111 | conditions, and telling the user how to view a copy of this General 112 | Public License. 113 | 114 | d) You may charge a fee for the physical act of transferring a 115 | copy, and you may at your option offer warranty protection in 116 | exchange for a fee. 117 | 118 | Mere aggregation of another independent work with the Program (or its 119 | derivative) on a volume of a storage or distribution medium does not bring 120 | the other work under the scope of these terms. 121 | 122 | 3. You may copy and distribute the Program (or a portion or derivative of 123 | it, under Paragraph 2) in object code or executable form under the terms of 124 | Paragraphs 1 and 2 above provided that you also do one of the following: 125 | 126 | a) accompany it with the complete corresponding machine-readable 127 | source code, which must be distributed under the terms of 128 | Paragraphs 1 and 2 above; or, 129 | 130 | b) accompany it with a written offer, valid for at least three 131 | years, to give any third party free (except for a nominal charge 132 | for the cost of distribution) a complete machine-readable copy of the 133 | corresponding source code, to be distributed under the terms of 134 | Paragraphs 1 and 2 above; or, 135 | 136 | c) accompany it with the information you received as to where the 137 | corresponding source code may be obtained. (This alternative is 138 | allowed only for noncommercial distribution and only if you 139 | received the program in object code or executable form alone.) 140 | 141 | Source code for a work means the preferred form of the work for making 142 | modifications to it. For an executable file, complete source code means 143 | all the source code for all modules it contains; but, as a special 144 | exception, it need not include source code for modules which are standard 145 | libraries that accompany the operating system on which the executable 146 | file runs, or for standard header files or definitions files that 147 | accompany that operating system. 148 | 149 | 4. You may not copy, modify, sublicense, distribute or transfer the 150 | Program except as expressly provided under this General Public License. 151 | Any attempt otherwise to copy, modify, sublicense, distribute or transfer 152 | the Program is void, and will automatically terminate your rights to use 153 | the Program under this License. However, parties who have received 154 | copies, or rights to use copies, from you under this General Public 155 | License will not have their licenses terminated so long as such parties 156 | remain in full compliance. 157 | 158 | 5. By copying, distributing or modifying the Program (or any work based 159 | on the Program) you indicate your acceptance of this license to do so, 160 | and all its terms and conditions. 161 | 162 | 6. Each time you redistribute the Program (or any work based on the 163 | Program), the recipient automatically receives a license from the original 164 | licensor to copy, distribute or modify the Program subject to these 165 | terms and conditions. You may not impose any further restrictions on the 166 | recipients' exercise of the rights granted herein. 167 | 168 | 7. The Free Software Foundation may publish revised and/or new versions 169 | of the General Public License from time to time. Such new versions will 170 | be similar in spirit to the present version, but may differ in detail to 171 | address new problems or concerns. 172 | 173 | Each version is given a distinguishing version number. If the Program 174 | specifies a version number of the license which applies to it and "any 175 | later version", you have the option of following the terms and conditions 176 | either of that version or of any later version published by the Free 177 | Software Foundation. If the Program does not specify a version number of 178 | the license, you may choose any version ever published by the Free Software 179 | Foundation. 180 | 181 | 8. If you wish to incorporate parts of the Program into other free 182 | programs whose distribution conditions are different, write to the author 183 | to ask for permission. For software which is copyrighted by the Free 184 | Software Foundation, write to the Free Software Foundation; we sometimes 185 | make exceptions for this. Our decision will be guided by the two goals 186 | of preserving the free status of all derivatives of our free software and 187 | of promoting the sharing and reuse of software generally. 188 | 189 | NO WARRANTY 190 | 191 | 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 192 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 193 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 194 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 195 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 196 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 197 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 198 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 199 | REPAIR OR CORRECTION. 200 | 201 | 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 202 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 203 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 204 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 205 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 206 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 207 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 208 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 209 | POSSIBILITY OF SUCH DAMAGES. 210 | 211 | END OF TERMS AND CONDITIONS 212 | 213 | Appendix: How to Apply These Terms to Your New Programs 214 | 215 | If you develop a new program, and you want it to be of the greatest 216 | possible use to humanity, the best way to achieve this is to make it 217 | free software which everyone can redistribute and change under these 218 | terms. 219 | 220 | To do so, attach the following notices to the program. It is safest to 221 | attach them to the start of each source file to most effectively convey 222 | the exclusion of warranty; and each file should have at least the 223 | "copyright" line and a pointer to where the full notice is found. 224 | 225 | 226 | Copyright (C) 19yy 227 | 228 | This program is free software; you can redistribute it and/or modify 229 | it under the terms of the GNU General Public License as published by 230 | the Free Software Foundation; either version 1, or (at your option) 231 | any later version. 232 | 233 | This program is distributed in the hope that it will be useful, 234 | but WITHOUT ANY WARRANTY; without even the implied warranty of 235 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 236 | GNU General Public License for more details. 237 | 238 | You should have received a copy of the GNU General Public License 239 | along with this program; if not, write to the Free Software 240 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA 241 | 242 | 243 | Also add information on how to contact you by electronic and paper mail. 244 | 245 | If the program is interactive, make it output a short notice like this 246 | when it starts in an interactive mode: 247 | 248 | Gnomovision version 69, Copyright (C) 19xx name of author 249 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 250 | This is free software, and you are welcome to redistribute it 251 | under certain conditions; type `show c' for details. 252 | 253 | The hypothetical commands `show w' and `show c' should show the 254 | appropriate parts of the General Public License. Of course, the 255 | commands you use may be called something other than `show w' and `show 256 | c'; they could even be mouse-clicks or menu items--whatever suits your 257 | program. 258 | 259 | You should also get your employer (if you work as a programmer) or your 260 | school, if any, to sign a "copyright disclaimer" for the program, if 261 | necessary. Here a sample; alter the names: 262 | 263 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 264 | program `Gnomovision' (a program to direct compilers to make passes 265 | at assemblers) written by James Hacker. 266 | 267 | , 1 April 1989 268 | Ty Coon, President of Vice 269 | 270 | That's all there is to it! 271 | 272 | 273 | --- The Artistic License 1.0 --- 274 | 275 | This software is Copyright (c) 2016 by Tatsuhiko Miyagawa . 276 | 277 | This is free software, licensed under: 278 | 279 | The Artistic License 1.0 280 | 281 | The Artistic License 282 | 283 | Preamble 284 | 285 | The intent of this document is to state the conditions under which a Package 286 | may be copied, such that the Copyright Holder maintains some semblance of 287 | artistic control over the development of the package, while giving the users of 288 | the package the right to use and distribute the Package in a more-or-less 289 | customary fashion, plus the right to make reasonable modifications. 290 | 291 | Definitions: 292 | 293 | - "Package" refers to the collection of files distributed by the Copyright 294 | Holder, and derivatives of that collection of files created through 295 | textual modification. 296 | - "Standard Version" refers to such a Package if it has not been modified, 297 | or has been modified in accordance with the wishes of the Copyright 298 | Holder. 299 | - "Copyright Holder" is whoever is named in the copyright or copyrights for 300 | the package. 301 | - "You" is you, if you're thinking about copying or distributing this Package. 302 | - "Reasonable copying fee" is whatever you can justify on the basis of media 303 | cost, duplication charges, time of people involved, and so on. (You will 304 | not be required to justify it to the Copyright Holder, but only to the 305 | computing community at large as a market that must bear the fee.) 306 | - "Freely Available" means that no fee is charged for the item itself, though 307 | there may be fees involved in handling the item. It also means that 308 | recipients of the item may redistribute it under the same conditions they 309 | received it. 310 | 311 | 1. You may make and give away verbatim copies of the source form of the 312 | Standard Version of this Package without restriction, provided that you 313 | duplicate all of the original copyright notices and associated disclaimers. 314 | 315 | 2. You may apply bug fixes, portability fixes and other modifications derived 316 | from the Public Domain or from the Copyright Holder. A Package modified in such 317 | a way shall still be considered the Standard Version. 318 | 319 | 3. You may otherwise modify your copy of this Package in any way, provided that 320 | you insert a prominent notice in each changed file stating how and when you 321 | changed that file, and provided that you do at least ONE of the following: 322 | 323 | a) place your modifications in the Public Domain or otherwise make them 324 | Freely Available, such as by posting said modifications to Usenet or an 325 | equivalent medium, or placing the modifications on a major archive site 326 | such as ftp.uu.net, or by allowing the Copyright Holder to include your 327 | modifications in the Standard Version of the Package. 328 | 329 | b) use the modified Package only within your corporation or organization. 330 | 331 | c) rename any non-standard executables so the names do not conflict with 332 | standard executables, which must also be provided, and provide a separate 333 | manual page for each non-standard executable that clearly documents how it 334 | differs from the Standard Version. 335 | 336 | d) make other distribution arrangements with the Copyright Holder. 337 | 338 | 4. You may distribute the programs of this Package in object code or executable 339 | form, provided that you do at least ONE of the following: 340 | 341 | a) distribute a Standard Version of the executables and library files, 342 | together with instructions (in the manual page or equivalent) on where to 343 | get the Standard Version. 344 | 345 | b) accompany the distribution with the machine-readable source of the Package 346 | with your modifications. 347 | 348 | c) accompany any non-standard executables with their corresponding Standard 349 | Version executables, giving the non-standard executables non-standard 350 | names, and clearly documenting the differences in manual pages (or 351 | equivalent), together with instructions on where to get the Standard 352 | Version. 353 | 354 | d) make other distribution arrangements with the Copyright Holder. 355 | 356 | 5. You may charge a reasonable copying fee for any distribution of this 357 | Package. You may charge any fee you choose for support of this Package. You 358 | may not charge a fee for this Package itself. However, you may distribute this 359 | Package in aggregate with other (possibly commercial) programs as part of a 360 | larger (possibly commercial) software distribution provided that you do not 361 | advertise this Package as a product of your own. 362 | 363 | 6. The scripts and library files supplied as input to or produced as output 364 | from the programs of this Package do not automatically fall under the copyright 365 | of this Package, but belong to whomever generated them, and may be sold 366 | commercially, and may be aggregated with this Package. 367 | 368 | 7. C or perl subroutines supplied by you and linked into this Package shall not 369 | be considered part of this Package. 370 | 371 | 8. The name of the Copyright Holder may not be used to endorse or promote 372 | products derived from this software without specific prior written permission. 373 | 374 | 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED 375 | WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF 376 | MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. 377 | 378 | The End 379 | 380 | --------------------------------------------------------------------------------