Skip to content

Instantly share code, notes, and snippets.

@yappo
Created April 7, 2016 18:31
Show Gist options
  • Save yappo/fc25aad3bdfe54204300db918fd41eb5 to your computer and use it in GitHub Desktop.
Save yappo/fc25aad3bdfe54204300db918fd41eb5 to your computer and use it in GitHub Desktop.
ikachan for LINE
use strict;
use warnings;
use DBI;
use Digest::SHA 'hmac_sha256_base64';
use Encode;
use Furl;
use JSON::PP;
use Plack::Request;
use String::Random;
my $dbh = DBI->connect('dbi:SQLite:dbname=lnyappo.db', '', '');
my $string_gen = String::Random->new;
my $furl = Furl->new( agent => 'LiNe Yappo/1.00' );
my $registration_secret = $ENV{LNYAPPO_REGISTRATION_SECRET}; # この LINE Bot は、ここで設定した文字列を受け付けると、ユーザの登録を行います
my $channel_id = $ENV{LINE_CHANNEL_ID};
my $channel_secret = $ENV{LINE_CHANNEL_SECRET};
my $bot_mid = $ENV{LINE_BOT_MID};
my $line_bot_api_endpoint = 'https://trialbot-api.line.me/v1/events';
sub {
my $env = shift;
my $req = Plack::Request->new($env);
my $res = $req->new_response(200);
if ($req->method eq 'POST') {
if ($req->path eq '/linebot/callback') {
if (signature_validation($req)) {
callback($req);
}
} elsif ($req->path eq '/send') {
do_send($req, $res);
}
} elsif ($req->method eq 'GET') {
if ($req->path eq '/') {
do_index($req, $res);
}
}
$res->finalize;
};
sub do_index {
my($req, $res) = @_;
$res->body(<<HTML);
<html>
<head><title>ln.yappo</title></head>
<body>
<h1>ln.yappo</h1>
<h2>API Spec</h2>
<table border="1">
<tr>
<td>endpoint</td>
<td>method://hostname:port/send</td>
</tr>
<tr>
<td>method</td>
<td>POST</td>
</tr>
<tr>
<td>Content-Type</td>
<td>application/x-www-form-urlencoded</td>
</tr>
<tr>
<td>params</td>
<td>
api_token: このアプリを動かしている LINE Bot と友達になり LNYAPPO_REGISTRATION_SECRET で設定した文章を LNE Bot に送信すると api_token が生成されるので、それを利用します。<br>
message: LINE に送信したいメッセージ
</td>
</tr>
<table>
<h2>quick send form</h2>
<form action="./send" method="post">
api_token: <input name="api_token"><br>
message: <input name="message"><br>
<input type="submit" value="send to LINE">
</form>
</body>
<html>
HTML
}
sub do_send {
my($req, $res) = @_;
my $api_token = $req->param('api_token');
my $message = $req->param('message');
unless ($api_token && $message) {
$res->body('{"status":500,"message":"Bad request."}');
return;
}
my $activate_mid = get_activate_mid_by_api_token($api_token);
unless ($activate_mid) {
$res->body('{"status":404,"message":"api_token is not exists."}');
return;
}
my $line_res = send_text($activate_mid->{mid}, decode( utf8 => $message ));
$res->header( 'Content-Type' => $line_res->header('Content-Type') );
$res->body($line_res->content);
}
sub signature_validation {
my $req = shift;
my $json = $req->content or return;
my $signature = $req->header('X-LINE-ChannelSignature');
$signature =~ s/=+\z//;
$signature eq hmac_sha256_base64($json, $channel_secret);
}
sub callback {
my $req = shift;
my $json = $req->content or return;
my $data = decode_json $json;
for my $message (@{ $data->{result} }) {
next unless validation_message($message);
if ($message->{content}{text} eq $registration_secret) {
registration($message, $message->{content}{text});
}
}
}
sub validation_message {
my $message = shift;
return unless $message->{eventType} eq '138311609000106303';
return unless $message->{fromChannel} eq '1341301815';
return unless $message->{toChannel} eq $channel_id;
return unless $message->{content}{toType} == 1;
return unless $message->{content}{contentType} == 1;
my $sent_me = 0;
for my $mid (@{ $message->{content}{to} }) {
$sent_me++ if $mid eq $bot_mid;
}
return unless $sent_me;
return 1;
}
sub registration {
my($message, $used_secret) = @_;
my $mid = $message->{content}{from};
my $activate_mid = get_activate_mid_by_mid($mid);
return send_text($mid, sprintf("You are registered.\nYour api_token is '%s'.", $activate_mid->{api_token})) if $activate_mid;
activate($mid, $used_secret);
}
sub get_activate_mid_by_mid {
my $mid = shift;
my $rows = $dbh->selectall_arrayref('SELECT * FROM activate_mid WHERE mid=?', { Slice => +{} }, $mid);
return $rows->[0];
}
sub get_activate_mid_by_api_token {
my $api_token = shift;
my $rows = $dbh->selectall_arrayref('SELECT * FROM activate_mid WHERE api_token=?', { Slice => +{} }, $api_token);
return $rows->[0];
}
sub activate {
my($mid, $used_secret) = @_;
for (1..5) {
my $api_token = $string_gen->randregex('[a-zA-Z0-9]{32}');
next if get_activate_mid_by_api_token($api_token);
$dbh->do(
'INSERT INTO activate_mid (mid, used_secret, api_token, created_at) VALUES(?, ?, ?, ?)', undef,
$mid, $used_secret, $api_token, time()
);
send_text($mid, "Congratulation! Your api_token is '$api_token'.");
return;
}
send_text($mid, 'Faild to registration.');
}
sub send_text {
my($mid, $text) = @_;
my $json = +{
to => [ $mid ],
toChannel => 1383378250,
eventType => 138311608800106203,
content => +{
contentType => 1,
toType => 1,
text => $text,
},
};
my $res = $furl->post($line_bot_api_endpoint, [
'Content-Type' => 'application/json; charset=UTF-8',
'X-Line-ChannelID' => $channel_id,
'X-Line-ChannelSecret' => $channel_secret,
'X-Line-Trusted-User-With-ACL' => $bot_mid,
], encode_json($json));
$res;
}
__END__
Usage
plackup --host $IP_ADDR -p $PORT lnyappo.pl
Description
LINE BOT API Trial を利用してシンプルな Web API を利用し、このアプリで紐付けた LINE アカウントに API に送信されたメッセージを送信することができます。
ikachan for LINE 的なものです。
1. https://business.line.me/ で必要な情報を設定する
2. LNYAPPO_REGISTRATION_SECRET に、好きな文字列を指定する
3. LINE_* の必要な項目を LINE の開発サイトからコピペして設定する
4. このアプリを適当な場所で起動する
5. LINE の開発者サイトに、このアプリの endpoint url を設定する https://hostname:443/linebot/callback とか
6. 作成した Bot と友達登録をする
7. その Bot に向けて LNYAPPO_REGISTRATION_SECRET を送る
8. Bot から設定された api_token が戻ってくるので、どこかで覚える
9. api_token を利用して、このアプリの API を叩けば LINE に任意のメッセージを送れます
for SQLite
$ sqlite3 lnyappo.db
CREATE TABLE activate_mid (
id INTEGER PRIMARY KEY AUTOINCREMENT,
mid TEXT NOT NULL UNIQUE,
used_secret TEXT NOT NULL UNIQUE,
api_token TEXT NOT NULL UNIQUE,
created_at INTEGER
);
CREATE INDEX activate_mid_mid ON activate_mid(mid);
CREATE INDEX activate_mid_api_token ON activate_mid(api_token);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment