#!perl use strict; use warnings; use utf8; use 5.008001; use App::Tacochan; use Amon2::Lite; use Skype::Any; use URI; use Getopt::Long (); use Pod::Usage; use Plack::Builder; use Plack::Builder::Conditionals -prefix => 'c'; use Twiggy::Server; my $skype = Skype::Any->new(name => 'tacochan'); sub render_text { my ($self, $code, $message) = @_; $message = $self->encoding->encode($message); return $self->create_response( $code, [ 'Content-Type' => 'text/plain; charset=utf-8', 'Content-Length' => length $message, ], [$message] ); } sub res_404 { $_[0]->render_text(404, 'Not Found') } sub guess_chat { my ($self, $stuff) = @_; if ($stuff =~ /^#/) { # from chatname return $skype->chat($stuff); } elsif ($stuff =~ /^skype:/) { # from "/get uri" my %query = URI->new($stuff)->query_form(); my $command = $skype->api->send_command("CHAT FINDUSINGBLOB $query{blob}"); my @reply = $command->split_reply(); return $skype->chat($reply[1]); } else { # from username return $skype->user($stuff)->chat(); } } get '/' => sub { my ($c) = @_; return $c->render('index.tt' => { ok => scalar $skype->api->is_running, }); }; get '/chat_list' => sub { my ($c) = @_; unless ($skype->api->is_running) { return $c->render_text(500, 'Skype is not running'); } my $command = $skype->api->send_command('SEARCH RECENTCHATS'); my ($obj, $chats) = $command->split_reply(2); my @recentchats; for my $chatname (split /,\s+/, $chats) { my $chat = $skype->chat($chatname); my $topic = $chat->topic; push @recentchats, { chatname => $chatname, status => $chat->status, members => [split /\s+/, $chat->members], $topic ? (topic => $topic) : (), }; } return $c->render_json(\@recentchats); }; post qr!^/(?:leave|part)$! => sub { my ($c) = @_; unless ($skype->api->is_running) { return $c->render_text(500, 'Skype is not running') } my $chat = $c->req->param('chat'); my $chat_obj = $c->guess_chat($chat); eval { $chat_obj->alter('leave'); }; if ($@) { return $c->render_text(403, "leave failure chat: $chat"); } return $c->render_text(200, "leave success chat: $chat"); }; post qr!^/(?:send|notice|privmsg)$! => sub { my ($c) = @_; unless ($skype->api->is_running) { return $c->render_text(500, 'Skype is not running') } my $chat = $c->req->param('chat'); my $message = $c->req->param('message'); my $chat_obj = $c->guess_chat($chat); eval { $chat_obj->send_message($message); }; if ($@) { return $c->render_text(403, "message sent failure chat: $chat $message"); } return $c->render_text(200, "message sent chat: $chat_obj->{id} $message"); }; get '/chat_id' => sub { my ($c) = @_; unless ($skype->api->is_running) { return $c->render_text(500, 'Skype is not running'); } my $chat = $c->req->param('chat'); my $chat_obj = $c->guess_chat($chat); return $c->render_text(200, "$chat_obj->{id}"); }; __PACKAGE__->load_plugin('Web::JSON'); my $parser = Getopt::Long::Parser->new( config => ['no_ignore_case', 'pass_through'], ); my %options; my ($http_host, $http_port) = ('127.0.0.1', 4969); my @reverse_proxy; $parser->getoptions( 'o|host=s' => \$http_host, 'p|port=i' => \$http_port, 'r|reverse-proxy=s' => \@reverse_proxy, 'h|help' => \$options{help}, 'v|version' => \$options{version}, ); pod2usage(1) if $options{help}; if ($options{version}) { die "tacochan $App::Tacochan::VERSION\n"; } my $app = builder { if (@reverse_proxy) { enable c_match_if c_addr(\@reverse_proxy), 'Plack::Middleware::ReverseProxy'; } enable 'Plack::Middleware::AccessLog', format => 'combined'; __PACKAGE__->to_app( handle_static => 1, no_x_content_type_options => 1, no_x_frame_options => 1, ); }; $skype->attach; warn "starting httpd: http://$http_host:$http_port/\n"; my $twiggy = Twiggy::Server->new( host => $http_host, port => $http_port, ); $twiggy->register_service($app); $skype->run; __DATA__ @@ index.tt tacochan

tacochan[% IF !ok %]...[% END %]

Skype message delivery by HTTP

[% IF !ok %]
Oops! tacochan doesn't work because Skype is not running. You need to manually start Skype and allow 'tacochan' to use Skype. After that restart tacochan.
[% END %]

Send a message to chat

See documentation on how to use it.

API documentations

Recent chats

method GET
url [% c().req.base %]chat_list
Content-Type application/json; charset=utf-8

Responds a list of recent chats as JSON (see below).

chatname : stringchatname
status : string"LEGACY_DIALOG"old style IM
"DIALOG"1:1 chat
"MULTI_SUBSCRIBED"participant in chat
"UNSUBSCRIBED"left chat
members : [string, ...]all users who have been there
topic : stringchat topic

Leave the chat

method POST
url [% c().req.base %]leave
form params chat=#chat|skype:|user[, user, ...]

Please be careful that /leave can only leave the chat. You can NOT re-join the chat yourself if you left already.

aliases: /part (for ikachan compatibility)

Send a message to chat

method POST
url [% c().req.base %]send
form params chat=#chat|skype:|user[, user, ...]&message=your_message

aliases: /notice, /privmsg (for ikachan compatibility)

Examples

#1 Send a message to 1:1 chat
echo123

Passes username to chat parameter. In this case, send a message to 'echo123'.

#2 Create a new group chat and send a message
echo123,anappo5

Passes usernames separated by ',' to chat parameter. In this case, create a new group chat to add 'echo123', 'anappo5'. If you don't want to create a new group chat, see #3 or #4.

#3 Send a message to an existing group chat (with chatname)
#anappo2/$d936403094338dbb

Passes chatname to chat parameter. Note that chatname is internal chat identifier. In tacochan, you can only get it from Recent chats.

#4 Send a message to an existing group chat (with Skype URI)
skype:?chat&lob=LsgqqqCTpxWYjt9PL1hSvGDOiPhqUuQAHxI7w7Qu7gJ3VZv_q_99ZJO4lF9Dfaw

Passes Skype URI to chat parameter. Note that if you want to get Skype URI (skype:), you need to send this message to the chat: /get uri

Chat ID

method GET
url [% c().req.base %]chat_id
form params chat=skype:|user[, user, ...]
Content-Type text/plain; charset=utf-8

Responds chat_id from Skype URI or username (chat_id is like this: #anappo2/$d936403094338dbb)

@@ /static/css/main.css .tacochan { padding: 0; } .tacochan article.container { width: auto; } header.header { margin: 0 auto 2% auto; padding-top: 5%; background: #d44413; color: #fff; } .error { background: #3b4653 !important; } table td.name { width: 150px; font-weight: bold; } div.docs { margin: 2% auto 0 auto; padding: 20px 0 5%; background: #eee; color: #000; } div.docs h3 a { color: #000; } div.docs table { background: #fff; } div.docs section.example { margin-bottom: 5px; padding: 10px; background: #fff; } __END__ =head1 NAME tacochan - Skype message delivery by HTTP =head1 SYNOPSIS % tacochan =head1 OPTIONS =over 4 =item -o, --host The interface a TCP based server daemon binds to. Defaults to undef, which lets most server backends bind the any (*) interface. This option doesn't mean anything if the server does not support TCP socket. =item -p, --port (default: 4969) The port number a TCP based server daemon listens on. Defaults to 4969. This option doesn't mean anything if the server does not support TCP socket. =item -r, --reverse-proxy Treat X-Forwarded-For as REMOTE_ADDR if REMOTE_ADDR match this argument. See L. =item -h, --help Show help for this command. =item -v, --version Show version. =back =cut