- #!/usr/bin/perl
- #
- # /usr/local/sbin/localmarkdown2sms
- # Copyright 2009 Jonas Smedegaard <dr@jones.dk>
- #
- # Send series of messages through Kannel from simplified Markdown files
- # * Lines starting with "#" are "keywords" activating a message series
- # * write only a single word
- # * use each keyword only once across the whole system
- # * use only minuscles (not majuscles, i.e. CAPITAL LETTERS)
- # * Lines starting with "##" express pauses
- # * a pause is a number + a single letter, without spaces between
- # * a pause line can contain multiple pauses, separated by space
- # Suggestion for writing style:
- #
- # * Write explicitly how to activate next series
- # * pick keywords tied to nex series rather than the previous
- # * use same instruction jargon across all series in the system
- use strict;
- use warnings;
- use Env qw[$debug $info $warn $dummy $nosleep];
- use Log::Log4perl qw(:easy);
- use File::Spec;
- use File::Slurp;
- use Time::Duration::Parse;
- use Encode;
- use LWP::UserAgent;
- use URI::Escape;
- use Proc::Daemon;
- # TODO: Use Coro instead
- Proc::Daemon::Init unless ($debug);
- my $sms_url = $ENV{SMS_URL} || "http://localhost:13013/cgi-bin/sendsms";
- my $sms_user = $ENV{SMS_USER} || "tester";
- my $sms_pw = $ENV{SMS_PW} || "foobar";
- my $sms_phone = $ENV{SMS_PHONE};
- my $sms_smsc = $ENV{SMS_SMSC};
- my $sms_msgtag = $ENV{SMS_MSGTAG} || "text";
- my (%file, %delay, %reply);
- my ($path) = shift @ARGV;
- my ($phone) = shift @ARGV;
- my ($key) = lc (shift @ARGV);
- # strip non-word chars from keyword (and use only first chunk of word chars)
- $key =~ s/.*?(\w+).*?/$1/;
- if ($debug) {
- Log::Log4perl->easy_init($DEBUG);
- } elsif ($INFO) {
- Log::Log4perl->easy_init($INFO);
- } elsif ($WARN) {
- Log::Log4perl->easy_init($WARN);
- } elsif ($ERROR) {
- Log::Log4perl->easy_init($ERROR);
- }
- foreach my $file (read_dir( $path )) {
- my ($key, $i, $skipkeysection, $skipcontent);
- # suppress repeated warnings for same issue
- my ($warn_nonkey_delay, $warn_nonkey_content);
- next unless ($file =~ /\.mdwn$/);
- foreach my $line (read_file( File::Spec->catfile($path, $file))) {
- chomp $line;
- my $content;
- # headline
- if ($line =~ /^(#+)\s*(.*?)\s*$/) {
- # tidy latest reply
- if (defined($key) and defined($reply{$key}[$i])) {
- $reply{$key}[$i] = &tidymsg($reply{$key}[$i]);
- ($reply{$key}[$i]) || delete $reply{$key}[$i];
- }
- my $level = length($1);
- $content = $2;
- # key
- if ($level == 1 and $content =~ /(\w+)/) {
- $key = lc($1);
- $i = 0;
- $skipkeysection = undef;
- $skipcontent = undef;
- if (lc($content) ne $key) {
- WARN "key \"$key\" extracted from fuzzy string \"$content\" in file \"$file\"";
- }
- if (!defined( $delay{$key})) {
- $delay{$key}[0] = 0;
- $warn_nonkey_delay = undef;
- $warn_nonkey_content = undef;
- } else {
- WARN "skipping non-unique key \"$key\" in file \"$file\"";
- $key = undef;
- $skipkeysection = 1;
- $skipcontent = 1;
- }
- # delay
- } elsif ($level == 2 and $content =~ /((\d+[sm]\s?)+)/) {
- $skipcontent = undef;
- if (defined( $key)) {
- my $delay = parse_duration($1);
- if (defined($reply{$key}[$i])) {
- $i++;
- $delay{$key}[$i] = $delay{$key}[$i - 1];
- }
- # $delay{$key}[$i] += $delay; # accumulate: forked replies
- $delay{$key}[$i] = $delay; # simple: queued replies
- if ($content ne $1) {
- WARN "delay (${delay}s) resolved from fuzzy string \"$content\" in file \"$file\"";
- }
- } elsif ($skipkeysection or $warn_nonkey_delay) {
- # skipping - already warned about it...
- } else {
- WARN "ignoring non-key'ed delay line \"$1\" in file \"$file\"";
- $warn_nonkey_delay = 1;
- $skipcontent = 1;
- }
- } else {
- WARN "ignoring non-parsable headline \"$line\" in file \"$file\"";
- $skipcontent = 1;
- }
- # reply
- } else {
- $content = $line . "\n";
- # ikiwiki directives - strip from content and parse for tags
- $content =~ s/(?<!\\)\[\[([^\[\]]*)(?<!\\)\]\]//gs and do {
- my $directive_string = $1;
- my ($directive, $directive_content);
- $directive_string =~ /^\s*\!(tag|taglink)\s*((\s*?\b\w+)+)/ and $file{$file}{'directive'}{'tag'} = [ split /\s+/, $2 ];
- };
- if ( defined( $key ) and not ($skipcontent)) {
- $reply{$key}[$i] .= $content;
- } elsif ($skipkeysection or $skipcontent or $warn_nonkey_content) {
- # skipping - already warned about it...
- } else {
- $content =~ /\S/s && WARN "skipping non-key'ed content \"$line\" in file \"$file\"";
- $warn_nonkey_content = 1;
- }
- }
- }
- # tidy latest reply
- if (defined($key) and defined($reply{$key}[$i])) {
- $reply{$key}[$i] = &tidymsg($reply{$key}[$i]);
- ($reply{$key}[$i]) || delete $reply{$key}[$i];
- }
- }
- sub tidymsg {
- my $msg = shift @_;
- $msg =~ s/^\h*$//g; # clean virtually empty lines
- $msg =~ s/(\S)\h$/$1/g; # strip single trailing space
- $msg =~ s/\n\n+/\n\n/g; # strip excess newlines
- $msg =~ s/(\S)\n([^\n])/$1 $2/g; # convert newline to space
- $msg =~ s/\h*$//g; # strip all trailing spaces
- $msg =~ s/^\s*(\w*?.*?)\s*$/$1/s; # strip surrounding space
- return $msg;
- }
- sub sendmsg {
- my ($phone, $desc, $msg) = @_;
- unless ($dummy) {
- my $ua = LWP::UserAgent->new(agent => "localmarkdown2sms");
- $ua->timeout(10);
- my $url = $sms_url
- . '?username=' . uri_escape($sms_user)
- . '&password=' . uri_escape($sms_pw)
- . '&to=' . uri_escape($phone);
- $url .= '&from=' . uri_escape($sms_phone) if ($sms_phone);
- $url .= '&smsc=' . uri_escape($sms_smsc) if ($sms_smsc);
- $url .= '&' . $sms_msgtag . '=' . uri_escape(encode("cp1252", $msg));
- my $response = $ua->request(HTTP::Request->new('GET', $url));
- unless ($response->is_success) {
- ERROR $response->status_line;
- }
- DEBUG "Done $desc";
- } else {
- print STDERR "\n --> $phone: $desc\n";
- print STDERR $msg . "\n";
- }
- }
- my $num_children = $#{ $reply{$key} } + 1; # How many children we'll create
- if (0 == $num_children) {
- &sendmsg($phone, "fallback message", "Sorry, the sms code \"$key\" is unknown.\nPlease send only sms codes to this number.");
- exit;
- }
- if ($debug) {
- DEBUG "queueing $num_children replies:";
- for my $num ( 0 .. $num_children - 1 ) {
- DEBUG " [" . $delay{$key}[$num] . "s]";
- }
- # DEBUG "\n";
- }
- for my $num ( 0 .. $num_children - 1 ) {
- sleep($delay{$key}[$num]) unless ($nosleep);
- &sendmsg($phone, "reply #$num [" . $delay{$key}[$num] . "s]", $reply{$key}[$num]);
- }
- 1;
|