summaryrefslogtreecommitdiff
path: root/localmarkdown2sms
blob: 0ca6abb8a3d37c3e2e525e2f861f35c23e71f863 (plain)
  1. #!/usr/bin/perl
  2. #
  3. # /usr/local/sbin/localmarkdown2sms
  4. # Copyright 2009 Jonas Smedegaard <dr@jones.dk>
  5. #
  6. # Send series of messages through Kannel from simplified Markdown files
  7. # * Lines starting with "#" are "keywords" activating a message series
  8. # * write only a single word
  9. # * use each keyword only once across the whole system
  10. # * use only minuscles (not majuscles, i.e. CAPITAL LETTERS)
  11. # * Lines starting with "##" express pauses
  12. # * a pause is a number + a single letter, without spaces between
  13. # * a pause line can contain multiple pauses, separated by space
  14. # Suggestion for writing style:
  15. #
  16. # * Write explicitly how to activate next series
  17. # * pick keywords tied to nex series rather than the previous
  18. # * use same instruction jargon across all series in the system
  19. use strict;
  20. use warnings;
  21. use Env qw[$debug $info $warn $dummy $nosleep];
  22. use Log::Log4perl qw(:easy);
  23. use File::Spec;
  24. use File::Slurp;
  25. use Time::Duration::Parse;
  26. use Proc::Fork;
  27. use Encode;
  28. use LWP::UserAgent;
  29. use URI::Escape;
  30. my $sms_url = "http://localhost:13013/cgi-bin/sendsms";
  31. my $sms_user = "tester";
  32. my $sms_pw = "foobar";
  33. my (%file, %delay, %reply);
  34. my ($path) = shift @ARGV;
  35. my ($phone) = shift @ARGV;
  36. my ($key) = lc (shift @ARGV);
  37. if ($debug) {
  38. Log::Log4perl->easy_init($DEBUG);
  39. } elsif ($INFO) {
  40. Log::Log4perl->easy_init($INFO);
  41. } elsif ($WARN) {
  42. Log::Log4perl->easy_init($WARN);
  43. } elsif ($ERROR) {
  44. Log::Log4perl->easy_init($ERROR);
  45. }
  46. foreach my $file (read_dir( $path )) {
  47. my ($key, $i, $skipkey, $skipcontent);
  48. # suppress repeated warnings for same issue
  49. my ($warn_nonkey_delay, $warn_nonkey_content);
  50. next unless ($file =~ /\.mdwn$/);
  51. foreach my $line (read_file( File::Spec->catfile($path, $file))) {
  52. chomp $line;
  53. my $content;
  54. # headline
  55. if ($line =~ /^(#+)\s*(.*?)\s*$/) {
  56. # tidy latest reply
  57. if (defined($key) and defined($reply{$key}[$i])) {
  58. $reply{$key}[$i] = &tidymsg($reply{$key}[$i]);
  59. ($reply{$key}[$i]) || delete $reply{$key}[$i];
  60. }
  61. my $level = length($1);
  62. $content = $2;
  63. # key
  64. if ($level == 1 and $content =~ /(\w+)/) {
  65. $key = lc($1);
  66. $i = 0;
  67. $skipkey = undef;
  68. $skipcontent = undef;
  69. if (lc($content) ne $key) {
  70. WARN "key \"$key\" extracted from fuzzy string \"$content\" in file \"$file\"";
  71. }
  72. if (!defined( $delay{$key})) {
  73. $delay{$key}[0] = 0;
  74. $warn_nonkey_delay = undef;
  75. $warn_nonkey_content = undef;
  76. } else {
  77. WARN "skipping non-unique key \"$key\" in file \"$file\"";
  78. $key = undef;
  79. $skipkey = 1;
  80. $skipcontent = 1;
  81. }
  82. # delay
  83. } elsif ($level == 2 and $content =~ /((\d+[sm](\s+|\Z))+)/) {
  84. $skipcontent = undef;
  85. if (defined( $key)) {
  86. my $delay = parse_duration($1);
  87. if (defined($reply{$key}[$i])) {
  88. $i++;
  89. $delay{$key}[$i] = $delay{$key}[$i - 1];
  90. }
  91. $delay{$key}[$i] += $delay;
  92. if ($content ne $1) {
  93. WARN "delay (${delay}s) resolved from fuzzy string \"$content\" in file \"$file\"";
  94. }
  95. } elsif ($skipkey) {
  96. # skipping - already warned about it...
  97. } else {
  98. WARN "ignoring non-key'ed delay line \"$1\" in file \"$file\"" unless ($warn_nonkey_delay);
  99. $warn_nonkey_delay = 1;
  100. $skipcontent = 1;
  101. }
  102. } else {
  103. WARN "ignoring non-parsable headline \"$line\" in file \"$file\"";
  104. $skipcontent = 1;
  105. }
  106. # reply
  107. } else {
  108. $content = $line . "\n";
  109. # ikiwiki directives - strip from content and parse for tags
  110. $content =~ s/(?<!\\)\[\[([^\[\]]*)(?<!\\)\]\]//gs and do {
  111. my $directive_string = $1;
  112. my ($directive, $directive_content);
  113. $directive_string =~ /^\s*\!(tag|taglink)\s*((\s*?\b\w+)+)/ and $file{$file}{'directive'}{'tag'} = [ split /\s+/, $2 ];
  114. };
  115. if ( defined( $key ) and not ($skipcontent)) {
  116. $reply{$key}[$i] .= $content;
  117. } elsif ($skipkey or $skipcontent) {
  118. # skipping - already warned about it...
  119. } else {
  120. WARN "skipping non-key'ed content \"$line\" in file \"$file\"" unless ($warn_nonkey_content);
  121. $warn_nonkey_content = 1;
  122. }
  123. }
  124. }
  125. # tidy latest reply
  126. if (defined($key) and defined($reply{$key}[$i])) {
  127. $reply{$key}[$i] = &tidymsg($reply{$key}[$i]);
  128. ($reply{$key}[$i]) || delete $reply{$key}[$i];
  129. }
  130. }
  131. sub tidymsg {
  132. my $msg = shift @_;
  133. $msg =~ s/^\h*$//g; # clean virtually empty lines
  134. $msg =~ s/(\S)\h$/$1/g; # strip single trailing space
  135. $msg =~ s/\n\n+/\n\n/g; # strip excess newlines
  136. $msg =~ s/(\S)\n([^\n])/$1 $2/g; # convert newline to space
  137. $msg =~ s/\h*$//g; # strip all trailing spaces
  138. $msg =~ s/^\s*(\w*?.*?)\s*$/$1/s; # strip surrounding space
  139. return $msg;
  140. }
  141. sub sendmsg {
  142. my ($phone, $desc, $msg) = @_;
  143. unless ($dummy) {
  144. my $ua = LWP::UserAgent->new(agent => "localmarkdown2sms");
  145. $ua->timeout(10);
  146. $ua->request(HTTP::Request->new('GET', $sms_url
  147. . '?username=' . uri_escape($sms_user)
  148. . '&password=' . uri_escape($sms_pw)
  149. . '&to=' . uri_escape($phone)
  150. . '&text=' . uri_escape(encode("cp1252", $msg))
  151. ));
  152. DEBUG "Done $desc";
  153. } else {
  154. print STDERR "\n --> $phone: $desc\n";
  155. print STDERR $msg . "\n";
  156. }
  157. }
  158. my $num_children = $#{ $reply{$key} } + 1; # How many children we'll create
  159. if (0 == $num_children) {
  160. &sendmsg($phone, "fallback message", "Hmmm, strange, the word \"$key\" is unknown. Perhaps you typed it wrong?\n\nPlease try again.");
  161. exit;
  162. }
  163. if (0 == $num_children) {
  164. }
  165. $SIG{CHLD} = 'IGNORE'; # Don't worry about reaping zombies
  166. # Spawn off some children
  167. if ($debug) {
  168. DEBUG "queueing $num_children replies:";
  169. for my $num ( 0 .. $num_children - 1 ) {
  170. DEBUG " [" . $delay{$key}[$num] . "s]";
  171. }
  172. # DEBUG "\n";
  173. }
  174. for my $num ( 0 .. $num_children - 1 ) {
  175. run_fork {
  176. child {
  177. sleep($delay{$key}[$num]) unless ($nosleep);
  178. &sendmsg($phone, "reply #$num [" . $delay{$key}[$num] . "s]", $reply{$key}[$num]);
  179. exit;
  180. }
  181. parent {
  182. if ($debug) {
  183. my $child_pid = shift;
  184. waitpid $child_pid, 0;
  185. }
  186. }
  187. }
  188. }
  189. 1;