3 # Copyright (c) 2012 ISHIKAWA Mutsumi <ishikawa@hanzubon.jp>
4 # This program is covered by the GNU General Public License 2
12 use HTTP::Request::Common;
18 my $class = ref $proto || $proto;
19 my $self = {cfg => shift};
28 my $fb = Facebook::Graph->new(app_id => $me->{cfg}->{fb_app_id},
29 secret => $me->{cfg}->{fb_app_secret},
30 postback => $me->{cfg}->{fb_postback_url});
32 my $res_token = $fb->request_access_token($me->{cfg}->{fb_access_code});
33 die 'token get error' if (!defined $res_token || !$res_token->response->is_success);
35 my $acts = $fb->fetch('me/accounts');
36 die 'can not get account list' if(!defined $acts || !$acts);
38 my $page_access_token = '';
39 foreach my $d (@{$acts->{data}}) {
40 if ($d->{id} eq $me->{cfg}->{fb_page_id}) {
41 $page_access_token = $d->{'access_token'};
44 die 'can not get access tokenfor page_id=' . $me->{cfg}->{fb_page_id} if ($page_access_token eq '');
46 return $me->{fbo} = Facebook::Graph->new(access_token => $page_access_token);
50 my ($me, $text, $uri) = @_;
52 return $me->{fbo}->add_post($me->{cfg}->{fb_page_id})
60 my ($me, $post_id) = @_;
62 return $me->{cfg}->{fb_page_url} . '/posts/' . $post_id;
66 my ($me, $post_id) = @_;
68 my $uri = $me->{fbo}->query->find($me->{cfg}->{fb_page_id}.'_'.$post_id)->uri_as_string;
69 my $req = HTTP::Request::Common::DELETE($uri);
70 warn Dumper($req) if ($me->{cfg}->{debug});
72 $resp = LWP::UserAgent->new->request($req);
73 warn Dumper($resp) if ($me->{cfg}->{debug});
74 if ($resp->is_success && $resp->code == 200 && $resp->content eq 'true') {
77 warn 'DELETE ERROR: http code: ' . $resp->code() . ' , http content: ' . $resp->content;
88 use DBI qw/:sql_types/;
94 my $class = ref $proto || $proto;
95 my $self = {cfg => shift};
104 return $me->{dbh} = DBI->connect('DBI:mysql:'.$me->{cfg}->{database}, $me->{cfg}->{db_user}, $me->{cfg}->{db_pass},{mysql_enable_utf8 => 1, mysql_auto_reconnect => 1}) || die $DBI::errstr;
108 my ($me, $db_args) = @_;
109 my $sth = $me->{dbh}->prepare('select * from posts where uri = ? order by post_time desc limit 1');
110 my $rv = $sth->execute($db_args->{uri});
111 my $ret = $sth->fetchrow_hashref;
118 my ($me, $db_args) = @_;
120 my ($scheme, $path) = split(/:\/\//, $db_args->{uri});
121 my $sth = $me->{dbh}->prepare("insert into posts (submitter, fb_post_id, uri, prefix, comment, scheme, path, post_time) values (?, ?, ?, ?, ?, ?, ?, ?)");
122 $sth->bind_param(1, $db_args->{submitter}, SQL_VARCHAR);
123 $sth->bind_param(2, $db_args->{fb_post_id}, SQL_BIGINT);
124 $sth->bind_param(3, $db_args->{uri}, SQL_VARCHAR);
125 $sth->bind_param(4, $db_args->{prefix}, SQL_VARCHAR);
126 $sth->bind_param(5, $db_args->{comment}, SQL_VARCHAR);
127 $sth->bind_param(6, $scheme, SQL_VARCHAR);
128 $sth->bind_param(7, $path, SQL_VARCHAR);
129 $sth->bind_param(8, time, SQL_BIGINT);
130 my $rv = $sth->execute();
137 my ($me, $db_args) = @_;
138 $db_args->{submitter_type} ||= 1;
140 my $sth = $me->{dbh}->prepare("delete from posts where fb_post_id = ? and submitter = ? and submitter_type = ?");
142 $sth->bind_param(1, $db_args->{fb_post_id}, SQL_BIGINT);
143 $sth->bind_param(2, $db_args->{submitter}, SQL_VARCHAR);
144 $sth->bind_param(3, $db_args->{submitter_type}, SQL_INTEGER);
145 my $rv = $sth->execute();
146 my $ret = $rv ? $sth->rows : 0;
154 my ($me, $db_args) = @_;
156 my $column = $db_args->{word} =~ /:\/\// ? 'uri' : 'path';
157 my $w = '%' . $db_args->{word} . '%';
158 my $sth = $me->{dbh}->prepare('select * from posts where prefix like ? or '.$column.' like ? or comment like ? order by post_time desc limit 1000');
159 $sth->bind_param(1, $w, SQL_VARCHAR);
160 $sth->bind_param(2, $w, SQL_VARCHAR);
161 $sth->bind_param(3, $w, SQL_VARCHAR);
164 my $ret = $sth->fetchall_arrayref({});
170 sub search_lastpost_by_submitter {
171 my ($me, $db_args) = @_;
173 my $sth = $me->{dbh}->prepare('select * from posts where submitter = ? order by post_time desc limit 1');
174 $sth->bind_param(1, $db_args->{who}, SQL_VARCHAR);
177 my $ret = $sth->fetchrow_hashref();
190 $me->{dbh}->rollback;
195 $me->{dbh}->begin_work;
204 use base 'Bot::BasicBot';
205 use POSIX 'strftime';
209 my $mu_re = qr/^([^\s]+)\s+((?:https?|ftps?):\/\/[^\s]+)\s+(.+)$/i;
215 $me->{last_search} = {};
220 $me->{fb} = Mubot4FB::FB->new($me->{cfg});
221 $me->{db} = Mubot4FB::DB->new($me->{cfg});
229 return $e->{submitter}.'が『'.$e->{prefix}.' '.$e->{uri}.' '.$e->{comment}.'』と'.strftime('%Y-%m-%d %H:%M:%S', localtime($e->{post_time}));
233 my ($me, $args, $msg) = @_;
235 $me->say(channel => $args->{channel},
241 my ($resp, $resp_msg);
243 if ($args->{body} =~ /$mu_re/) {
247 my $text = $args->{who} . '曰く、'.$prefix.' '.$comment;
249 if (my $res = $me->{db}->check_dup({uri =>$uri})) {
250 if ($res->{post_time} < time() - 7 * 24 * 60 * 60) {
251 $resp_msg = 'だいぶ前 '.$me->_format_submit($res).'にいってたにゃー '.$me->{fb}->post_uri($res->{fb_post_id});
253 $resp_msg = '既に '.$me->_format_submit($res).'に言ってますよ? '.$me->{fb}->post_uri($res->{fb_post_id});
257 eval{$resp = $me->{fb}->publish($text, $uri)};
260 eval{$resp = $me->{fb}->publish($text, $uri)};
261 $post_ok = 0 if ($@);
265 my (undef, $post_id) = split(/_/, $resp->{id});
266 $me->{db}->add({submitter => $args->{who},
267 fb_post_id => $post_id,
270 comment => $comment});
271 $resp_msg = $args->{who} . ': うい '.$me->{fb}->post_uri($post_id).' で登録';
273 $resp_msg = 'can not post to facebook';
282 my ($me, $args) = @_;
284 my $last_post = $me->{db}->search_lastpost_by_submitter({who => $args->{who}});
286 if (!defined $last_post) {
287 return $args->{who}.': いまのっていつの? というか ないし';
288 } elsif ($last_post->{post_time} < time() - 3600) {
289 return $args->{who}.': いまのっていつの? 最後のはこれだけど古いんだにゃ ' . $me->{fb}->post_uri($last_post->{fb_post_id});
291 return $me->_remove($args, $last_post->{'fb_post_id'});
296 my ($me, $args, $post_id) =@_;
297 my ($resp_msg, $resp);
300 if ($resp = $me->{db}->remove({fb_post_id => $post_id, submitter => $args->{who}})) {
301 # fb 側のエントリを削除しないといけない
302 if ($me->{fb}->remove($post_id)) {
304 $resp_msg = $args->{who} . ': 削除しました ' . $me->{fb}->post_uri($post_id);
307 $resp_msg = $args->{who} . ': 削除に失敗しましたよ? ' . $me->{fb}->post_uri($post_id);
311 $resp_msg = $args->{who} . ': そんな投稿ないよ? ' . $me->{fb}->post_uri($post_id);
317 my ($me, $args) = @_;
319 if ($args->{body} =~ /^ふみくん\s+(.+)\?\s*$/) {
320 $me->{last_search}->{$args->{who}} = undef;
321 $me->{last_search}->{$args->{who}} = $me->{db}->search_by_word({word => $1});
322 return $me->_search_next($args);
327 my ($me, $args) = @_;
329 my $resp_msg = 'ないっす';
330 if (defined $me->{last_search}->{$args->{who}}) {
331 my $ent = pop($me->{last_search}->{$args->{who}});
333 my $count = @{$me->{last_search}->{$args->{who}}};
336 $resp_msg = $args->{who} . ': ' . $me->_format_submit($ent).'に言ってた '.($count ? '[ほか'.$count.'件] ' : '[ほかにはもうないよ] ').$me->{fb}->post_uri($ent->{fb_post_id});
347 my ($me, $args) = @_;
350 if ($args->{body} =~ /$mu_re/) {
351 $resp_msg = $me->_add($args) unless ($1 eq 'deb');
352 } elsif ($args->{body} =~ /^ふみくん\s+(.+)\s*$/) {
354 if ($cmd eq 'いまのなし') {
355 $resp_msg = $me->_remove_prev($args);
356 } elsif ($cmd =~ /削除\s+(?:$me->{cfg}->{fb_page_url}\/posts\/)?([0-9]+)$/) {
357 $resp_msg = $me->_remove($args, $1);
358 } elsif ($cmd =~ /\?$/) {
359 $resp_msg = $me->_search_start($args);
360 } elsif ($cmd =~ /つぎ/) {
361 $resp_msg = $me->_search_next($args);
362 } elsif ($cmd =~ /どこ/) {
363 $resp_msg = $args->{who}.': ここ ' . $me->{cfg}->{fb_page_url};
365 $resp_msg = $args->{who}.': ん? (' . strftime('%Y-%m-%d %H:%M:%S', localtime) . ')';
369 $me->_response($args, $resp_msg) if ($resp_msg);
377 use Hash::Merge::Simple;
378 use Data::Recursive::Encode;
382 my $config_name = $ARGV[0] || 'not_found';
385 my $config_path = ['/etc/mubot4fb/'.$config_name,
386 $ENV{HOME} . '/.mubot4fb/'. $config_name,
387 $ENV{PWD} . '/mubot4fb_' . $config_name];
389 my $c = Config::Any->load_stems({stems => $config_path, use_ext => 1, flatten_to_hash => 1});
390 foreach my $i (keys %$c) {
391 $cfg = Hash::Merge::Simple->merge($cfg, $c->{$i});
393 die 'missing config file' unless (keys %$cfg);
395 die 'missing some config parameters should be defined (irc_server, fb_app_id, fb_app_secret, fb_access_code, fb_page_id fb_postback_url)'
396 if (!defined $cfg->{'irc_server'}
397 || !defined $cfg->{'fb_app_id'}
398 || !defined $cfg->{'fb_app_secret'}
399 || !defined $cfg->{'fb_access_code'}
400 || !defined $cfg->{'fb_page_id'}
401 || !defined $cfg->{'fb_postback_url'}
402 || !defined $cfg->{'db_user'}
403 || !defined $cfg->{'db_pass'}
405 $cfg = Data::Recursive::Encode->decode('utf8', $cfg);
407 $cfg->{irc_port} ||= 6667;
408 $cfg->{irc_channels} ||= ['#mubot4fb'];
409 $cfg->{irc_nick} ||= 'mubot4fb';
410 $cfg->{irc_name}||= $cfg->{irc_nick};
411 $cfg->{irc_charset} ||= 'utf8';
412 $cfg->{database} ||= 'mubot4fb';
415 my $bot = Mubot4FB->new(server => $cfg->{'irc_server'},
416 port => $cfg->{'irc_port'},
417 channels => $cfg->{'irc_channels'},
418 nick => $cfg->{'irc_nick'},
419 username => $cfg->{'irc_name'},
420 name => $cfg->{'irc_name'},
421 charset => $cfg->{'irc_charset'},