File Coverage

blib/lib/Net/MyPeople/Bot.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Net::MyPeople::Bot;
2 1     1   35591 use 5.010;
  1         4  
  1         43  
3 1     1   992 use utf8;
  1         11  
  1         7  
4 1     1   442 use Moose;
  0            
  0            
5             use namespace::autoclean;
6             use Data::Dumper;
7             use LWP::UserAgent;
8             use LWP::Protocol::https;
9             use HTTP::Request::Common;
10             use JSON;
11             use Data::Printer;
12             use URI::Escape;
13             use File::Util qw(SL);
14             use Encode qw(is_utf8 _utf8_off);
15             use Log::Log4perl qw(:easy);
16             Log::Log4perl->easy_init($ERROR);
17              
18             # ABSTRACT: Implements MyPeople-Bot.
19              
20             our $VERSION = '0.320'; # VERSION
21              
22              
23              
24             has apikey=>(
25             is=>'rw',
26             required=>1
27             );
28              
29              
30             has web_proxy_base=>(
31             is=>'rw',
32             );
33              
34             has ua=>(
35             is=>'ro',
36             default=>sub{return LWP::UserAgent->new;},
37             );
38              
39              
40              
41             our $API_BASE = 'https://apis.daum.net/mypeople';
42             our $API_SEND = $API_BASE . '/buddy/send.json';
43             our $API_BUDDY = $API_BASE . '/profile/buddy.json';
44             our $API_GROUP_MEMBERS = $API_BASE . '/group/members.json';
45             our $API_GROUP_SEND = $API_BASE . '/group/send.json';
46             our $API_GROUP_EXIT = $API_BASE . '/group/exit.json';
47             our $API_FILE_DOWNLOAD = $API_BASE . '/file/download.json';
48              
49             our $API_SEND_LENGTH = 1000;
50              
51             sub BUILD {
52             my $self = shift;
53             }
54              
55             sub _call_file {
56             my $self = shift;
57             my ($apiurl, $param, $path) = @_;
58             $apiurl .= '?apikey='.uri_escape($self->apikey);
59             $apiurl = $self->web_proxy_base.$apiurl if $self->web_proxy_base;
60              
61             my $req = POST( $apiurl, Content=>$param );
62             DEBUG $req->as_string;
63             my $res = $self->ua->request( $req );
64              
65             if( $res->is_success ){
66             my $sl = SL;
67             $path =~ s@$sl$@@;
68             my $filepath;
69             if( -d $path ){
70             $filepath = $path.SL.$res->filename;
71             }
72             else{
73             $filepath = $path;
74             }
75             DEBUG $filepath;
76             open my $fh, '>', $filepath;
77             binmode($fh);
78             print $fh $res->content;
79             close $fh;
80             return $filepath;
81             }
82             else{
83             ERROR p $res;
84             return undef;
85             }
86             }
87             sub _call_multipart {
88             my $self = shift;
89             my ($apiurl, $param) = @_;
90             $apiurl .= '?apikey='.$self->apikey;
91             $apiurl = $self->web_proxy_base.$apiurl if $self->web_proxy_base;
92              
93             #foreach my $k (keys %{$param}){
94             # $param->{$k} = uri_escape($param->{$k});
95             #}
96              
97             my $req = POST( $apiurl,
98             Content_Type => 'form-data',
99             Content => $param
100             );
101             DEBUG $req->as_string;
102              
103             my $res = $self->ua->request($req);
104             DEBUG p $res;
105              
106             if( $res->is_success ){
107             return from_json( $res->content , {utf8 => 1} );
108             }
109             else{
110             ERROR p $res;
111             return undef;
112             }
113             }
114             sub _call {
115             my $self = shift;
116             my ($apiurl, $param) = @_;
117             $apiurl .= '?apikey='.uri_escape($self->apikey);
118             $apiurl = $self->web_proxy_base.$apiurl if $self->web_proxy_base;
119              
120             my $req = POST( $apiurl,
121             #Content_Type => 'form-data',
122             Content=>$param
123             );
124             DEBUG $req->as_string;
125             my $res = $self->ua->request( $req );
126             DEBUG p $res;
127            
128             if( $res->is_success ){
129             return from_json( $res->content , {utf8 => 1} );
130             }
131             else{
132             ERROR p $res;
133             return undef;
134             }
135             }
136              
137              
138             sub buddy{
139             my $self = shift;
140             my ($buddyId) = @_;
141             return $self->_call($API_BUDDY, {buddyId=>$buddyId} );
142             }
143              
144              
145             sub groupMembers{
146             my $self = shift;
147             my ($groupId) = @_;
148             return $self->_call($API_GROUP_MEMBERS, {groupId=>$groupId} );
149             }
150              
151              
152             sub send{
153             my $self = shift;
154             my ($buddyId, $content, $attach_path, $do_not_split) = @_;
155             if( $attach_path && -f $attach_path ){
156             return $self->_call_multipart($API_SEND, [buddyId=>$buddyId, attach=>[$attach_path]] );
157             }
158             else{
159             my @chunks;
160             if( !$do_not_split && length $content > $API_SEND_LENGTH ){
161             @chunks = split(/(.{$API_SEND_LENGTH})/, $content);
162             }
163             else{
164             push(@chunks,$content);
165             }
166              
167             my $res;
168             foreach my $chunk (@chunks){
169             _utf8_off($chunk) if is_utf8 $chunk;
170             $res = $self->_call($API_SEND, {buddyId=>$buddyId, content=>$chunk} );
171             }
172             return $res;
173             }
174             }
175              
176              
177             sub groupSend{
178             my $self = shift;
179             my ($groupId, $content, $attach_path, $do_not_split) = @_;
180             if( $attach_path && -f $attach_path ){
181             return $self->_call_multipart($API_GROUP_SEND, [groupId=>$groupId, attach=>[$attach_path]] );
182             }
183             else{
184             my @chunks;
185             if( !$do_not_split && length $content > $API_SEND_LENGTH ){
186             @chunks = split(/(.{$API_SEND_LENGTH})/, $content);
187             }
188             else{
189             push(@chunks,$content);
190             }
191              
192             my $res;
193             foreach my $chunk (@chunks){
194             _utf8_off($chunk) if is_utf8 $chunk;
195             $res = $self->_call($API_GROUP_SEND, {groupId=>$groupId, content=>$chunk} );
196             }
197             return $res;
198             }
199             }
200              
201              
202             sub groupExit{
203             my $self = shift;
204             my ($groupId) = @_;
205             return $self->_call($API_GROUP_EXIT, {groupId=>$groupId} );
206             }
207              
208              
209             sub fileDownload{
210             my $self = shift;
211             my ($fileId, $path) = @_;
212             return $self->_call_file($API_FILE_DOWNLOAD, {fileId=>$fileId} , $path);
213             }
214              
215             __PACKAGE__->meta->make_immutable;
216             1;
217              
218             __END__
219              
220             =pod
221              
222             =head1 NAME
223              
224             Net::MyPeople::Bot - Implements MyPeople-Bot.
225              
226             =head1 VERSION
227              
228             version 0.320
229              
230             =head1 SYNOPSIS
231              
232             #!/usr/bin/env perl
233              
234             use strict;
235             use warnings;
236             use utf8;
237              
238             use Net::MyPeople::Bot;
239             use AnyEvent::HTTPD;
240             use Data::Printer;
241             use JSON;
242             use Log::Log4perl qw(:easy);
243             Log::Log4perl->easy_init($DEBUG); # you can see requests in Net::MyPeople::Bot.
244              
245             my $APIKEY = 'OOOOOOOOOOOOOOOOOOOOOOOOOO';
246             my $bot = Net::MyPeople::Bot->new(apikey=>$APIKEY);
247              
248             # You should set up callback url with below informations. ex) http://MYSERVER:8080/callback
249             my $httpd = AnyEvent::HTTPD->new (port => 8080);
250             $httpd->reg_cb (
251             '/'=> sub{
252             my ($httpd, $req) = @_;
253             $req->respond( { content => ['text/html','hello'] });
254             },
255             '/callback' => sub {
256             my ($httpd, $req) = @_;
257              
258             my $action = $req->parm('action');
259             my $buddyId = $req->parm('buddyId');
260             my $groupId = $req->parm('groupId');
261             my $content = $req->parm('content');
262              
263             callback( $action, $buddyId, $groupId, $content );
264             }
265             );
266              
267             sub callback{
268             my ($action, $buddyId, $groupId, $content ) = @_;
269             p @_;
270              
271             if ( $action eq 'addBuddy' ){ # when someone add this bot as a buddy.
272             # $buddyId : buddyId who adds this bot to buddys.
273             # $groupId : ""
274             # $content : buddy info for buddyId
275             # [
276             # {"buddyId":"XXXXXXXXXXXXXXXXXXXX","isBot":"N","name":"XXXX","photoId":"myp_pub:XXXXXX"},
277             # ]
278              
279             my $buddy = from_json($content)->[0]; #
280             my $buddy_name = $buddy->{buddys}->{name};
281             my $res = $bot->send($buddyId, "Nice to meet you, $buddy_name");
282              
283             }
284             elsif( $action eq 'sendFromMessage' ){ # when someone send a message to this bot.
285             # $buddyId : buddyId who sends message
286             # $groupId : ""
287             # $content : text
288              
289             my @res = $bot->send($buddyId, "$content");
290             if($content =~ /^myp_pci:/){
291             $bot->fileDownload($content,'./sample.jpg');
292             # you can also download a profile image with buddy's photoId,'myp_pub:XXXXXXX'
293             }
294             if($content =~ /sendtest/){
295             $bot->send($buddyId,undef,'./sample.jpg');
296             }
297             if($content =~ /buddytest/){
298             my $buddy = $bot->buddy($buddyId);
299             #{"buddys":[{"buddyId":"XXXXXXXXXXXXXXX","name":"XXXX","photoId":"myp_pub:XXXXXXXXXXXXXXX"}],"code":"200","message":"Success"}
300             $bot->send($buddyId, to_json($buddy));
301             }
302             }
303             elsif( $action eq 'createGroup' ){ # when this bot invited to a group chat channel.
304             # $buddyId : buddyId who creates
305             # $groupId : new group id
306             # $content : members
307             # [
308             # {"buddyId":"XXXXXXXXXXXXXXXXXXXX","isBot":"N","name":"XXXX","photoId":"myp_pub:XXXXXX"},
309             # {"buddyId":"XXXXXXXXXXXXXXXXXXXX","isBot":"N","name":"XXXX","photoId":"myp_pub:XXXXXX"},
310             # {"buddyId":"XXXXXXXXXXXXXXXXXXXX","isBot":"Y","name":"XXXX","photoId":"myp_pub:XXXXXX"}
311             # ]
312              
313             my $members = from_json($content);
314             my @names;
315             foreach my $member (@{$members}){
316             next if $member->{isBot} eq 'Y';# bot : The group must have only one bot. so, isBot='Y' means bot itself.
317             push(@names, $member->{name});
318             }
319              
320             my $res = $bot->groupSend($groupId, (join(',',@names)).'!! Nice to meet you.');
321            
322             }
323             elsif( $action eq 'inviteToGroup' ){ # when someone in a group chat channel invites user to the channel.
324             # $buddyId : buddyId who invites member
325             # $groupId : group id where new member is invited
326             # $content :
327             # [
328             # {"buddyId":"XXXXXXXXXXXXXXXXXXXX","isBot":"N","name":"XXXX","photoId":"myp_pub:XXXXXX"},
329             # {"buddyId":"XXXXXXXXXXXXXXXXXXXX","isBot":"Y","name":"XXXX","photoId":"myp_pub:XXXXXX"}
330             # ]
331             my $invited = from_json($content);
332             my @names;
333             foreach my $member (@{$invited}){
334             next if $member->{isBot} eq 'Y';
335             push(@names, $member->{name});
336             }
337             my $res = $bot->groupSend($groupId, (join(',',@names))."!! Can you introduce your self?");
338              
339             }
340             elsif( $action eq 'exitFromGroup' ){ # when someone in a group chat channel leaves.
341             # $buddyId : buddyId who exits
342             # $groupId : group id where member exits
343             # $content : ""
344              
345             my $buddy = $bot->buddy($buddyId); # hashref
346             my $buddy_name = $buddy->{buddys}->[0]->{name};
347             my $res = $bot->sendGroup($groupId, "I'll miss $buddy_name ...");
348              
349             }
350             elsif( $action eq 'sendFromGroup'){ # when received from group chat channel
351             # $buddyId : buddyId who sends message
352             # $groupId : group id where message is sent
353             # $content : text
354              
355             if( $content eq 'bot.goout' ){ # a reaction for an user defined command, 'bot.goout'
356             my $res = $bot->groupSend($groupId, 'Bye~');
357             $res = $bot->groupExit($groupId);
358             }
359             elsif($content =~ /membertest/){
360             my $members= $bot->groupMembers($groupId);
361             $bot->groupSend($groupId, to_json($members));
362             }
363             else{
364              
365             my $res = $bot->groupSend($groupId, "(GROUP_ECHO) $content");
366             }
367             }
368             }
369             print "Bot is started\n";
370             $httpd->run;
371              
372             =head1 DESCRIPTION
373              
374             MyPeople is an instant messenger service of Daum Communications in Republic of Korea (South Korea).
375              
376             MyPeople Bot is API interface of MyPeople.
377              
378             If you want to use this bot API,
379             Unfortunately,you must have an account for http://www.daum.net.
380             And you can understand Korean.
381              
382             =head2 PROPERTIES
383              
384             =over 4
385              
386             =item apikey
387              
388             required. put here MyPeople Bot APIKEY.
389              
390             =item web_proxy_base
391              
392             optional. If you don't have public IP, use L<https://github.com/sng2c/mypeople-bot-buffer> and put here as 'http://HOST:IP/proxy/'.
393             All of API urls are affected like 'http://HOST:IP/proxy/http://...'.
394              
395             =back
396              
397             =head2 METHODS
398              
399             =over 4
400              
401             =item $res = $self->buddy( BUDDY_ID )
402              
403             get infomations of a buddy.
404              
405             returns buddy info.
406              
407             {
408             "buddys":
409             [
410             {
411             "buddyId":"XXXXXXXXXXXXXXX",
412             "name":"XXXX",
413             "photoId":
414             "myp_pub:XXXXXXXXXXXXXXX"
415             }
416             ],
417             "code":"200",
418             "message":"Success"
419             }
420              
421             =item $res = $self->groupMembers( GROUP_ID )
422              
423             Get members in a group.
424              
425             returns infos of members in the GROUP.
426              
427             {
428             "buddys":
429             [
430             {
431             "buddyId":"XXXXXXXXXXXXXXX",
432             "name":"XXXX",
433             "photoId":
434             "myp_pub:XXXXXXXXXXXXXXX"
435             },
436             {
437             "buddyId":"XXXXXXXXXXXXXXX",
438             "name":"XXXX",
439             "photoId":
440             "myp_pub:XXXXXXXXXXXXXXX"
441             },
442              
443             ...
444             ],
445             "code":"200",
446             "message":"Success"
447             }
448              
449             =item $res = $self->send( BUDDY_ID, TEXT )
450              
451             =item $res = $self->send( BUDDY_ID, TEXT, undef, $do_not_split )
452              
453             =item $res = $self->send( BUDDY_ID, undef, FILEPATH )
454              
455             send text to a buddy.
456              
457             If you set FILEPATH, it sends the file to the buddy.
458              
459             returns result of request.
460              
461             =item $res = $self->groupSend( GROUP_ID, TEXT )
462              
463             =item $res = $self->groupSend( GROUP_ID, TEXT, undef, $do_not_split )
464              
465             =item $res = $self->groupSend( GROUP_ID, undef, FILEPATH )
466              
467             send text to a group.
468              
469             If you set FILEPATH, it sends the file to the group.
470              
471             returns result of request.
472              
473             =item $res = $self->groupExit( GROUP_ID )
474              
475             exit from a group.
476              
477             returns result of request.
478              
479             =item $res = $self->fileDownload( FILE_ID, DIRPATH_OR_FILEPATH )
480              
481             download attached file with FILE_ID.
482              
483             If you set directory path on second argument, the file is named automatically by 'Content-Disposition' header.
484              
485             returns path of the file saved.
486              
487             =back
488              
489             =head2 CALLBACK
490              
491             See SYNOPSIS.
492              
493             =head1 SEE ALSO
494              
495             =over
496              
497             =item *
498              
499             MyPeople : L<https://mypeople.daum.net/mypeople/web/main.do>
500              
501             =item *
502              
503             MyPeople Bot API Home : L<http://dna.daum.net/apis/mypeople>
504              
505             =back
506              
507             =head1 AUTHOR
508              
509             khs <sng2nara@gmail.com>
510              
511             =head1 COPYRIGHT AND LICENSE
512              
513             This software is copyright (c) 2013 by khs.
514              
515             This is free software; you can redistribute it and/or modify it under
516             the same terms as the Perl 5 programming language system itself.
517              
518             =cut