File:  [Local Repository] / mail2lj / mail2lj.pl
Revision 1.10: download - view: text, annotated - select for diffs - revision graph
Tue May 6 00:42:15 2014 UTC (10 years, 1 month ago) by boris
Branches: MAIN
CVS tags: HEAD
Added

    1: #! /usr/bin/perl -w
    2: #
    3: # The script to post mail messages to LiveJournal
    4: # (see http://mail2lj.nichego.net/ for original).
    5: # 
    6: # Changes by LG (all are labelled by '# Changed by LG' string):
    7: #   - Removed all references to Mail2LJ::Config and $cfg (just as author's
    8: #     comment below says).
    9: #   - Changed $host definition.
   10: #   - Changed location of mimemtmp subdirectory from $HOME to /tmp
   11: #   - Changed location and name of log file to $HOME/mail/mail2lj.log
   12: #   - In bounces and responces replaced charset from Windows-1251 to koi8-r
   13: #   - Added comment-parsing settings (keyword Comments: can be "no" or "off"
   14: #     to forbid comments, or "noemail" to not email comments).  If not set, 
   15: #     falls back to Journal's Default, obviously.
   16: #   - Removed "[mail2lj]" label in the subject.
   17: #
   18: # ! - Added command line parsing.  Now all the keywords can be specified
   19: #     on the command line (see '-h' for help).  Collected options are passed
   20: #     on to the posting subroutine and *override* corresponding body keywords
   21: #     values (e.g., now you can specify '--usejournal' when posting via 
   22: #     'hpost-(user)-(MD5Hash)' alias).  As an added bonus, now it's possible
   23: #     to post COMPLETELY without body keywords (via either 'post', 
   24: #     'post-(user)-(password) or 'hpost-(user)-(MD5Hash)' aliases), so you 
   25: #     can use the script as a general purpose mail-to-LJ-anywhere gateway.
   26: #     E.g. it'll work great in procmail.
   27: #
   28: # ! - Changed recipient of bounce messages in send_bounce() function to allow
   29: #     optional designation of custom error recipient (as opposed to strictly 
   30: #     original From: address).  This is convenient when you want to notify 
   31: #     script maintainer instead of the poster (exactly what I need).
   32: #
   33: # Changes by Boris Veytsman - added --cut option
   34: #
   35: # Changes by LG: added --obfuscate option to protect e-mails in the body.
   36: #
   37: # NB: to generate MD5 hash of your password, use the following command:
   38: #     perl -MDigest::MD5 -e 'print Digest::MD5::md5_hex("yourpassword")."\n"'
   39: #
   40: #
   41: # Adopted by Lev Gorenstein <lev@ledorub.poxod.com> from the original
   42: # script by jason@nichego.net (http://livejournal.com/users/jsn/) which 
   43: # is available at http://mail2lj.nichego.net/
   44: #
   45: # Original script seems to be distributed as freeware, so I stick to that
   46: # decision.  No warranty whatsoever, of course - use at your own risk ;-).
   47: #
   48: # ------------------------------------------------------------------------
   49: 
   50: use	strict ;
   51: 
   52: use	Getopt::Long;
   53: use	LWP::UserAgent ;
   54: use	HTTP::Request ;
   55: use	URI::Escape ;
   56: use	MIME::Parser ;
   57: use	MIME::Words qw/decode_mimewords encode_mimeword/ ;
   58: use	Unicode::MapUTF8 qw/to_utf8 from_utf8 utf8_charset_alias/ ;
   59: use	HTML::TokeParser ;
   60: 
   61: # Changed by LG - commented out configs.
   62: # use	Mail2LJ::Config ; # you can just remove every line mentioning
   63: # 			  # Mail2LJ::Config or $cfg
   64: # 
   65: # my	$cfg = $Mail2LJ::Config::conf ;
   66: 
   67: # Changed by LG - added shorname and version.
   68: (my $shortname = $0) =~ s/^.*\///; 		# script name without path
   69: my $Version = "0.9"; 				# Version number
   70: my $LGmod   = "-LG"; 				# Version modifier by LG
   71: 
   72: 
   73: my	$post_uri = "http://www.livejournal.com/cgi-bin/log.cgi" ;
   74: my	$ljcomment_action = 'http://www.livejournal.com/talkpost_do.bml';
   75: # my	$host = $ENV{MAIL2LJ_DOMAIN} || "mail2lj.nichego.net" ; # Changed by LG
   76: # my	$host = $ENV{MAIL2LJ_DOMAIN} || `hostname -f` ;         # Changed by LG
   77: my	$host = $ENV{MAIL2LJ_DOMAIN} || "ledorub.poxod.com" ;   # Changed by LG
   78: # my	$home = $ENV{HOME} || "/home/mail2lj" ; 		# Changed by LG
   79: my	$home = $ENV{HOME} || "/tmp/mail2lj" ;
   80: 
   81: # Changed by LG - added because sometimes procmail doesn't set $USER.
   82: my 	$SysUser = $ENV{USER} || $ENV{LOGNAME} || getpwuid($>) || $> ;
   83: 
   84: # Changed by LG.  Specifies the default incoming and outgoing charset for
   85: # all e-mails (i.e, the posts CONTENT and the script replies).  
   86: # For incoming mails, the MIME header is analyzed and actual MIME charset
   87: # overrides the default, of course.
   88: # my 	$MailCharset = "cp1251";
   89: my 	$MailCharset = "koi8-r";
   90: 
   91: # Changed by LG.  Specifies the charset in which non-English characters
   92: # FROM THE COMMAND LINE are entered.  I.e. if I give a command line option
   93: # '--subject ôÅÓÔ', the script needs to know the encoding to properly convert
   94: # it to UTF8.  I'm too lazy to analyze current locale, so I'll make it the
   95: # user's responsibility.  Override via '--charset' option.
   96: # my	$SystemCharset = "cp1251";
   97: # my	$SystemCharset = "utf8";
   98: my	$SystemCharset = "koi8-r";
   99: 
  100: 
  101: # Translation table for smstrip_data() function.  Only used whith aliases
  102: # ljreply-... and ljreplys-...
  103: my %tr = (
  104: 'á' => 'A', 'â' => 'B', '÷' => 'V', 'ç' => 'G', 'ä' => 'D', 'å' => 'E', '³' =>
  105: 'E', 'ö' => 'Zh', 'ú' => 'Z', 'é' => 'I', 'ê' => 'J', 'ë' => 'K', 'ì' => 'L',
  106: 'í' => 'M', 'î' => 'N', 'ï' => 'O', 'ð' => 'P', 'ò' => 'R', 'ó' => 'S', 'ô' =>
  107: 'T', 'õ' => 'U', 'æ' => 'F', 'è' => 'H', 'ã' => 'C', 'þ' => 'Ch', 'ý' => 'Sch',
  108: 'û' => 'Sh', 'ø' => '\'', 'ù' => 'Y', 'ÿ' => '\'', 'ü' => 'E', 'à' => 'Yu',
  109: 'ñ' => 'Ya', 'Á' => 'a', 'Â' => 'b', '×' => 'v', 'Ç' => 'g', 'Ä' => 'd', 'Å' =>
  110: 'e', '£' => 'e', 'Ö' => 'zh', 'Ú' => 'z', 'É' => 'i', 'Ê' => 'i', 'Ë' => 'k',
  111: 'Ì' => 'l', 'Í' => 'm', 'Î' => 'n', 'Ï' => 'o', 'Ð' => 'p', 'Ò' => 'r', 'Ó' =>
  112: 's', 'Ô' => 't', 'Õ' => 'u', 'Æ' => 'f', 'È' => 'h', 'Ã' => 'c', 'Þ' => 'ch',
  113: 'Û' => 'sh', 'Ý' => 'sch', 'Ø' => '\'', 'Ù' => 'y', 'ß' => '\'', 'Ü' => 'e',
  114: 'À' => 'yu', 'Ñ' => 'ya'
  115: );
  116: 
  117: # What to convert '@' to when obfuscating e-mail addresses (in '--add-from'
  118: # and/or '--obfuscate' modes.
  119: my $newdog = '[_@_]';
  120: 
  121: # ------------------------------------------------------------------------ #
  122: # End configuration settings.
  123: # ------------------------------------------------------------------------ #
  124: 
  125: 
  126: # ------------------------------------------------------------------------ #
  127: # Changed by LG - added parsing of command line.
  128: # Changed by BV - added options cut
  129: # ------------------------------------------------------------------------ #
  130: my 	%Opt = (); 			# Main options go here
  131: my 	$opt_h ; 			# Help flag
  132: my 	$opt_bounces ; 			# Alternative error recipient flag
  133: my 	$opt_addfrom ; 			# Add the From field to the post
  134: my 	$opt_addfromh ; 		# Add the htmlized From to the post
  135: my 	$opt_keepspaces ; 		# HTML-encode multiple spaces in e-mail
  136: my 	@opt_taglist ; 			# command-line taglist first goes here
  137: my 	$opt_ljcut ;    		# Add lj-cut after line number N
  138: my 	$ljcut_delta = 5 ; 		# No lj-cut if less lines left after it
  139: my 	$opt_ljcut_text ; 		# A text for lj-cut.
  140: my 	$opt_obfuscate ;    		# Obfuscate e-mail addresses in body
  141: my 	$Parse = GetOptions( \%Opt,
  142: 			'user|u=s',
  143: 			'password|passwd|p=s',
  144: 			'hpassword|hpasswd|hp=s',
  145: 			'date|d=s',
  146: 			'security|sec=s',
  147: 			'prop_opt_preformatted|formatted|f!',
  148: 			'prop_opt_backdated|backdated|back-dated|backdate|back-date|back!',
  149: 			'subject|subj|s=s',
  150: 			'taglist|tags|tag|t=s' => \@opt_taglist,  # Will tweak
  151: 			'notaglist|notags|notag|not|no-taglist|no-tags|no-tag|no-t' => sub {undef @opt_taglist},
  152: 			'usejournal|use-journal|use|journal|j=s',
  153: 			'prop_current_mood|current_mood|mood=s',
  154: 			'prop_current_music|current_music|music=s',
  155: 			'prop_picture_keyword|picture_keyword|picture|pic|userpic=s',
  156: 			'comments|comment|c=s', 	# Will tweak below
  157: 			'charset|enc=s' => \$SystemCharset,
  158: 			'bounces|bounce|b=s' => \$opt_bounces,
  159: 			'addfrom|add-from|from!' => \$opt_addfrom,
  160: 			'addfromh|add-fromh|fromh!' => \$opt_addfromh,
  161: 			'ljcut|lj-cut|cut|l=i'=>\$opt_ljcut,
  162: 			'ljcut-text|lj-cut-text|cut-text|ljcuttext|cuttext=s'=>\$opt_ljcut_text,
  163: 			'keep-spaces|keep-space|keepspaces|keepspace|spaces|space!' => \$opt_keepspaces,
  164: 			'obfuscate|obfu|o!' => \$opt_obfuscate,
  165: 			'help|h' => \$opt_h,
  166: 			   );
  167: 
  168: # Handle bad options
  169: if ( ! $Parse ) {
  170:    print_usage('short');
  171:    die "Run with '-h' for more help.\n\n";
  172: }
  173: 
  174: # Print help if requested.
  175: print_usage('long'), exit 0   if ($opt_h);
  176: 
  177: 
  178: # Check if '--date' was specified and convert hash value to proper format
  179: # for LJ request.
  180: if ( exists $Opt{'date'} ) {
  181:    # Note: "DD.MM.YYYY HH:MM".  Single-digit day, month and hour are allowed.
  182:    # Double-digit "YY" is also allowed and considered "2000 + YY"
  183:    if ( $Opt{'date'} =~ /(\d\d?)\.(\d\d?)\.(\d{2,4})\s+(\d\d?):(\d\d)/ ) {
  184: 	$Opt{'day'} = $1 ;
  185: 	$Opt{'mon'} = $2 ;
  186: 	$Opt{'year'} = $3 ;
  187: 	$Opt{'hour'} = $4 ;
  188: 	$Opt{'min'} = $5 ;
  189: 	$Opt{'year'} += 2000 if $Opt{'year'} < 100 ;
  190:    } else {
  191: 	print STDERR "can't parse date '$Opt{'date'}', using current.\n" ;
  192:    }
  193:    delete $Opt{'date'} ; 		# And remove the old element.
  194: }
  195: 
  196: 
  197: 
  198: # Comments option is 'comments yes/no/nomail', but LJ wants 
  199: # 'prop_opt_*no*comments' property.  Keep command line human-readable and
  200: # switch to proper value in the hash.
  201: if ( exists $Opt{'comments'} ) {
  202:    if ( $Opt{'comments'} =~ /^s*((on)|(yes)|(default))\s*$/i ) {
  203:       $Opt{'prop_opt_nocomments'} = "" ;
  204:    } elsif ( $Opt{'comments'} =~ /^\s*(noe?mails?)\s*$/i ) {
  205:       $Opt{'prop_opt_nocomments'} = "" ;
  206:       $Opt{'prop_opt_noemail'} = 1 ;
  207:    } elsif ( $Opt{'comments'} =~ /^\s*((off)|(no))\s*$/i ) {
  208:       $Opt{'prop_opt_nocomments'} = 1 ;
  209:    } else {
  210:       $Opt{'prop_opt_nocomments'} = $Opt{'comments'} ;
  211:    }
  212:    delete $Opt{'comments'} ; 		# And remove the old element.
  213: }
  214: 
  215: 
  216: # Convert taglist array into a single string and store it 
  217: # with other parameters.
  218: $Opt{'prop_taglist'} = join( ", ", @opt_taglist )  if ( @opt_taglist ) ;
  219: 
  220: # Convert $opt_ljcut_text to UTF8.
  221: $opt_ljcut = 0  unless defined $opt_ljcut ; 		# Safety
  222: if ( defined $opt_ljcut_text ) {
  223:    $opt_ljcut_text = 
  224: 	to_utf8({ -string => $opt_ljcut_text, -charset => $SystemCharset }) ;
  225: }
  226: 
  227: # Convert all %Opt command line options to unicode.
  228: # Function href2utf8() uses a reference to input hash, so %Opt is
  229: # being modified "in-place".
  230: href2utf8( \%Opt, $SystemCharset) ;
  231: 
  232: 
  233: # Changed by LG - set a restrictive umask (we're talking mail files here!)
  234: umask 077 ;
  235: 
  236: 
  237: # Changed by LG: make sure that 'UTF-8' is recognized as a valid charset
  238: # along with "UTF8" ;-)
  239: utf8_charset_alias({ 'UTF-8' => 'UTF8' });
  240: 
  241: 
  242: # Changed by LG - moved from above.
  243: my	$alias = shift @ARGV || "none" ;
  244: my	$mp = new MIME::Parser() or die "new MIME::Parser(): $!\n" ;
  245: 
  246: 
  247: # Changed by LG - changed directory to be user and process-specific.
  248: # $mp->output_dir("$home/mimetmp") ;
  249: $mp->output_dir("/tmp/mimetmp-" . $SysUser . "-$$") ;
  250: mkdir $mp->output_dir if not -d $mp->output_dir ; 	# Create it if missing
  251: 
  252: # Get the whole mail.
  253: # Changed by LG - added removal of output directory.
  254: my	$me = $mp->parse(\*STDIN) ;
  255: END	{ $me and $me->purge() ;
  256: 	  rmdir $mp->output_dir if -d $mp->output_dir 
  257: 		or print STDERR "Error removing $mp->output_dir: $!\n" ;
  258: 	} ;
  259: 
  260: 
  261: # Changed by LG -  different log file name.
  262: # open(STDERR, ">>$home/generic.log") or die "open(`log'): $!\n" ;
  263: my $logdir = "$home/mail" ;
  264: mkdir $logdir  if not -d $logdir ; 			# Create it if missing
  265: open(STDERR, ">>$logdir/mail2lj.log") or die "open(`log'): $!\n" ;
  266: 
  267: my	$users = {} ;
  268: # $users = $cfg->{users} ;
  269: 
  270: # Get mail header.
  271: my	$mh = $me->head() ;
  272: $me->dump_skeleton(\*STDERR) ;
  273: 
  274: # Changed by LG - added chomping of "To:" field.
  275: my $to = $me->get('To') || "" ; 
  276: chomp $to ;
  277: print STDERR "Alias: $alias\n", "To: $to\n",
  278: 	  "Charset: ", $mh->mime_attr("content-type.charset") || "NONE", "\n" ;
  279: 
  280: my	$xmailer = $mh->get('X-Mailer') || "unknown" ;
  281: if ($xmailer =~ /EPOC/ || $xmailer =~ /Eudora.+PalmOS/) {
  282: 	# too bad. they do violate standards there.
  283: 	$mh->mime_attr("content-type.charset" => "windows-1251") ;
  284: 	print STDERR "Charset changed to 'windows-1251' (hopefully)\n" ;
  285: }
  286: 
  287: 
  288: # And here we do posting.
  289: if ($alias =~ /MAILER-DAEMON/i) {
  290: 	exit 0 ;
  291: } elsif ($alias =~ /^post$/) {
  292: 	# my	$req = post_me2req($me, "windows-1251") ; 	# Changed by LG
  293: 	my	$req = post_me2req($me, "$MailCharset", { %Opt }) ; 	# Changed by LG
  294: 	my	$ljres = submit_request($req) ;
  295: 
  296: 	if ($ljres->{'success'} eq "OK") {
  297: 	    print STDERR "journal updated successfully\n" ;
  298: 	} else {
  299: 	    print STDERR "error updating journal: $ljres->{errmsg}\n" ;
  300: 	    send_bounce($ljres->{errmsg}, $me, $mh->mime_attr("content-type.charset")) ;
  301: 	}
  302: } elsif ($alias =~ /^post-(\w+)-(\w+)$/) {
  303: 	my	$l = $1 ;
  304: 	my	$p = $2 ;
  305: 	# my	$req = post_me2req($me, "windows-1251", { 	# Changed by LG
  306: 	#	    user => $l,
  307: 	#	    password => $p
  308: 	my	$req = post_me2req($me, "$MailCharset", { 	# Changed by LG
  309: 		    user => $l,
  310: 		    password => $p,
  311: 		    %Opt 					# Changed by LG
  312: 		}) ;
  313: 	my	$ljres = submit_request($req) ;
  314: 
  315: 	if ($ljres->{'success'} eq "OK") {
  316: 	    print STDERR "journal updated successfully\n" ;
  317: 	} else {
  318: 	    print STDERR "error updating journal: $ljres->{errmsg}\n" ;
  319: 	    send_bounce($ljres->{errmsg}, $me, $mh->mime_attr("content-type.charset")) ;
  320: 	}
  321: } elsif ($alias =~ /^hpost-(\w+)-(\w+)$/) {
  322: 	my	$l = $1 ;
  323: 	my	$hp = $2 ;
  324: 	# my	$req = post_me2req($me, "windows-1251", { 	# Changed by LG
  325: 	#	    user => $l,
  326: 	#	    hpassword => $hp
  327: 	my	$req = post_me2req($me, "$MailCharset", { 	# Changed by LG
  328: 		    user => $l,
  329: 		    hpassword => $hp,
  330: 		    %Opt 					# Changed by LG
  331: 		}) ;
  332: 	my	$ljres = submit_request($req) ;
  333: 
  334: 	if ($ljres->{'success'} eq "OK") {
  335: 	    print STDERR "journal updated successfully\n" ;
  336: 	} else {
  337: 	    print STDERR "error updating journal: $ljres->{errmsg}\n" ;
  338: 	    send_bounce($ljres->{errmsg}, $me, $mh->mime_attr("content-type.charset")) ;
  339: 	}
  340: } elsif ($alias =~ /^ljreply-(\S+)$/ || $alias =~ /^ljreplys-(\S+)$/) {
  341: 	my	$email = $1 ;
  342: 	$email =~ s/\.\./\@/ ;
  343: 
  344: 	if ($mh->get('From') !~ m/lj_dontreply\@livejournal.com/ && 
  345: 	    $mh->get('From') !~ m/lj_notify\@livejournal.com/) {
  346: 	    # someone just picked our email from livejournal.com site
  347: 	    print STDERR "no livejournal signature found, bouncing to $email\n";
  348: 	    $mh->replace('To', $email) ;
  349: 	    $me->send("sendmail") ;
  350: 	    exit 0 ;
  351: 	}
  352: 
  353: 	die "ljreply doesn't look like a 2-part message.\n"
  354: 	    unless $me->parts() == 2 ;
  355: 	my	$formdata = ljcomment_form2string
  356: 	    $me->parts(1)->bodyhandle->as_string() ;
  357: 	# Changed by LG - changed to a variable.
  358: 	# my	$charset =
  359: 	# 	($me->parts(0)->head->mime_attr('content-type.charset') ||
  360:         #       "windows-1251") ;
  361: 	my	$charset =
  362: 		($me->parts(0)->head->mime_attr('content-type.charset') ||
  363:                 "$MailCharset") ;
  364: 	my	$data = $me->parts(0)->bodyhandle->as_string() ;
  365: 
  366: 	my	$nicefrom = "Mail2LJ-translated comment" ;
  367: 	if ($mh->get("From") =~ /\(([^\)]+)\)/) {
  368: 	    $nicefrom = $1 ;
  369: 	}
  370: 	print STDERR "nicefrom is '$nicefrom'\n" ;
  371: 
  372: 	if ($alias =~ /^ljreplys/) {
  373: 	    print STDERR "stripping content...\n" ;
  374: 	    $data = to_utf8({ -string => $data, -charset => $charset})
  375: 		if $charset !~ /^utf-?8$/i ;
  376: 	    # Changed by LG - changed to a variable.
  377: 	    # $data = from_utf8({ -string => $data, -charset => "cp1251"}) ;
  378: 	    # $charset = "windows-1251" ;
  379: 	    $data = from_utf8({ -string => $data, -charset => "$MailCharset"}) ;
  380: 	    $charset = "$MailCharset" ;
  381: 	    $data = smstrip_data $data ;
  382: 	}
  383: 
  384: 	my	$msg = build MIME::Entity(
  385: 	    'From'         => "ljfrom-$formdata\@$host",
  386: #	    'Sender'	   => "ljfrom-$formdata\@$host",
  387: 	    'To'           => $email,
  388: 	    'Subject'      => normalize_header($mh->get('Subject'), $charset),
  389: 	    'Content-Type' => "text/plain; charset=$charset" ,
  390: 	    'Data'         => $data
  391: 	);
  392: 	$msg->send("sendmail") ;
  393: 	$msg->purge() ;
  394: } elsif ($alias =~ /^ljfrom-(\S+)$/) {
  395: 	my	$formdata = $1 ;
  396: 	my	$hr = ljcomment_string2form($formdata) ;
  397: 	my	$req = new HTTP::Request('POST' => $ljcomment_action)
  398: 		or die "new HTTP::Request(): $!\n" ;
  399: 
  400: 	$hr->{usertype} = 'user' ;
  401: 	# Changed by LG.
  402: 	# $hr->{encoding} = $mh->mime_attr('content-type.charset') ||
  403: 	# 		  "cp1251" ;
  404: 	$hr->{encoding} = $mh->mime_attr('content-type.charset') ||
  405: 			  "$MailCharset" ;
  406: 	$hr->{subject}  = decode_mimewords($mh->get('Subject'));
  407: 	$hr->{body} = $me->bodyhandle->as_string() ;
  408: 
  409: 	$req->content_type('application/x-www-form-urlencoded');
  410: 	$req->content(href2string($hr)) ;
  411: 
  412: 	my      $ljres = submit_request($req, "comment") ;
  413: 
  414: 	if ($ljres->{'success'} eq "OK") {
  415: 	    print STDERR "journal updated successfully\n" ;
  416: 	} else {
  417: 	    print STDERR "error updating journal: $ljres->{errmsg}\n" ;
  418: 	    send_bounce($ljres->{errmsg}, $me, $mh->mime_attr("content-type.charset")) ;
  419: 	}
  420: }
  421: print STDERR "-------------------------------------------------------------\n" ;
  422: 
  423: 
  424: # ------------------------------------------------------------------------- #
  425: # All done.
  426: # ------------------------------------------------------------------------- #
  427: exit 0 ;
  428: 
  429: 
  430: 
  431: # ------------------------------------------------------------------------- #
  432: # Subroutines from now down.
  433: # ------------------------------------------------------------------------- #
  434: sub	href2utf8 {
  435: 	my	($hr, $e) = @_ ;
  436: 	my	$i ;
  437: 
  438: 	foreach $i (keys %$hr) {
  439: 	    $hr->{$i} = to_utf8({ -string => $hr->{$i}, -charset => $e}) ;
  440: 	}
  441: 	return $hr ;
  442: }
  443: 
  444: sub	href2string {
  445: 	my	$hr = shift ;
  446: 	my	$i ;
  447: 	my	$s = "" ;
  448: 
  449: 	foreach $i (keys %$hr) {
  450: 	    next if $i eq "event" ;
  451: 	    $s .= "&" if $s ;
  452: 	    $s .= $i . "=" . uri_escape($hr->{$i}, "^A-Za-z0-9") ;
  453: 	}
  454: 
  455: 	if ($hr->{event}) {
  456: 	    $s .= "&" if $s ;
  457: 	    $s .= "event=" . uri_escape($hr->{event}, "^A-Za-z0-9") ;
  458: 	}
  459: 	return $s ;
  460: }
  461: 
  462: sub	post_body2href {
  463: 	my	$fh = shift ;
  464: 	my	($l, $auth) ;
  465: 	my	$req_data = {
  466: 	    webversion			=> 'full',
  467: 	    ver				=> 1,
  468: 	    security			=> 'public',
  469: 	    prop_opt_preformatted	=> 0,
  470: 	    mode			=> 'postevent'
  471: 	} ;
  472: 
  473: 	while ($l = $fh->getline()) {
  474: 	    if (exists $req_data->{event}) {
  475: 		$req_data->{event} .= $l ;
  476: 		next ;
  477: 	    }
  478: 
  479: 	    next if $l =~ /^$/ ;
  480: 
  481: 	    if ($l =~ /^(\w[\w_]*[\w])\s*[=:]\s*(\S.*)$/) {
  482: 		my	($var, $val) = (lc($1), $2) ;
  483: 
  484: 		if ($var eq "date") {
  485: 		    # Changed by LG.
  486:    		    # Note: "DD.MM.YYYY HH:MM".  Single-digit day, month and
  487:    		    # hour are allowed.  Double-digit "YY" is also allowed 
  488:    		    # and considered "2000 + YY".
  489: 		    if ($val =~ /(\d\d?)\.(\d\d?)\.(\d{2,4})\s+(\d\d?):(\d\d)/) {
  490: 			$req_data->{day} = $1 ;
  491: 			$req_data->{mon} = $2 ;
  492: 			$req_data->{year} = $3 ;
  493: 			$req_data->{hour} = $4 ;
  494: 			$req_data->{min} = $5 ;
  495: 			$req_data->{year} += 2000 if $req_data->{year} < 100 ;
  496: 		    } else {
  497: 			print STDERR "can't parse date '$val', will use current\n" ;
  498: 		    }
  499: 		} elsif ($var eq "mood" || $var eq "current_mood") {
  500: 		    $req_data->{prop_current_mood} = $val ;
  501: 		} elsif ($var eq "music" || $var eq "current_music") {
  502: 		    $req_data->{prop_current_music} = $val ;
  503: 		} elsif ($var eq "picture" || $var eq "picture_keyword") {
  504: 		    $req_data->{prop_picture_keyword} = $val ;
  505: 		} elsif ($var eq "formatted" || $var eq "autoformat") {
  506: 		    $val = 1 if $val =~ /^\s*((on)|(yes))\s*$/i ;
  507: 		    $val = 0 if $val =~ /^\s*((off)|(no))\s*$/i ;
  508: 		    # Changed by LG - "autoformat" is opposite to "formatted".
  509: 		    # Add 0 to make sure it's the number.
  510: 		    $val = 0 + (not $val)  if ($var eq "autoformat") ;
  511: 		    $req_data->{prop_opt_preformatted} = $val ;
  512: 		} elsif ($var eq "auth") {
  513: 		    $auth = $val ;
  514: 
  515: 		# Changed by LG - added 'backdated' option.  Remember,
  516: 		# Livejournal currently prohibits backdated entries in the
  517: 		# communities (as opposed to individual journals).
  518: 		} elsif ($var =~ /^back-?dated?$/ || $var eq "opt_backdated") {
  519: 		    $val = 1 if $val =~ /^\s*((on)|(yes))\s*$/i ;
  520: 		    $val = 0 if $val =~ /^\s*((off)|(no))\s*$/i ;
  521: 		    $req_data->{prop_opt_backdated} = $val ;
  522: 
  523: 		# Changed by LG - added comment-parsing settings.
  524: 		# Comments: default/on/yes | off/no | nomail 
  525: 		# Assembled based on data from form values in the browser 
  526: 		# and from info on
  527: 		# http://www.livejournal.com/doc/server/ljp.csp.flat.postevent.html
  528: 		# http://www.livejournal.com/doc/server/ljp.csp.proplist.html
  529: 		} elsif ($var eq "comments" || $var eq "comment" 
  530: 			|| $var eq "comment_settings"
  531: 			|| $var eq "comments_settings" ) {
  532: 		    if ( $val =~ /^\s*((on)|(yes)|(default))\s*$/i ) {
  533: 			# Journal default
  534: 		        $val = "" ;
  535: 			$req_data->{comment_settings} = $val ;
  536: 			$req_data->{prop_opt_nocomments} = $val ;
  537: 		    } elsif ( $val =~ /^\s*(noe?mails?)\s*$/i ) {
  538: 			# No emails
  539: 			$val = "1" ;
  540: 			$req_data->{prop_opt_nocomments} = (not $val) + 0;
  541: 		        $req_data->{prop_opt_noemail} = $val ;
  542: 		    } elsif ( $val =~ /^\s*((off)|(no))\s*$/i ) {
  543: 			# No comments
  544: 			$val = "1" ;
  545: 			$req_data->{prop_opt_nocomments} = $val ;
  546:                     } else {
  547: 			# Anything else.
  548: 			$req_data->{comment_settings} = $val ;
  549: 		    }
  550: 
  551: 		# Changed by LG - added 'tags' option.  
  552: 		} elsif ($var =~ /^tags?$/ || $var eq "taglist") {
  553: 		    $req_data->{prop_taglist} = $val;
  554: 
  555: 		# Changed by LG - added 'notags' option.  Empty the preceding
  556: 		# taglist if set to true, otherwise do nothing 
  557: 		} elsif ($var =~ /^no-?tags?$/ || $var eq "no-?taglist") {
  558: 		    $req_data->{prop_taglist} = "" if $val =~ /^\s*((on)|(yes))\s*$/i ;
  559: 
  560: 		# Changed by LG - added 'Obfuscate' option to protect e-mail
  561: 		# addresses in the body of the message.
  562: 		} elsif ($var =~ /^obfuscate$/ ) {
  563: 		    $val = 1 if $val =~ /^\s*((on)|(yes))\s*$/i ;
  564: 		    $val = 0 if $val =~ /^\s*((off)|(no))\s*$/i ;
  565: 		    $opt_obfuscate = $val ;
  566: 
  567: 		# Anything else - just assign.
  568: 		} else {
  569: 		    $req_data->{$var} = $val ;
  570: 		}
  571: 	    } else {
  572: 		$req_data->{event} = $l ;
  573: 	    }
  574: 	}
  575: 
  576: 	if (!exists $req_data->{year}) {
  577: 	    my	@lt = localtime() ;
  578: 	    $req_data->{day} = $lt[3] ;
  579: 	    $req_data->{mon} = $lt[4] + 1 ;
  580: 	    $req_data->{year} = 1900 + $lt[5] ;
  581: 	    $req_data->{hour} = $lt[2] ;
  582: 	    $req_data->{min} = $lt[1] ;
  583: 	}
  584: 
  585: 	if ($auth) {
  586: 	    $req_data->{password} = $users->{$req_data->{user}}->{password}
  587: 		if exists $users->{$req_data->{user}} &&
  588: 		    $users->{$req_data->{user}}->{auth} eq $auth ;
  589: 	}
  590: 
  591: 	return $req_data ;
  592: }
  593: 
  594: sub	hdr2utf8 {
  595: 	my	($s, $e) = @_ ;
  596: 	my	$r = "" ;
  597: 	my	$i ;
  598: 
  599: 	foreach $i (decode_mimewords $s) {
  600: 	    $r .= to_utf8({
  601: 		-string => $i->[0],
  602: 		-charset => ($i->[1] || $e)
  603: 	    }) ;
  604: 	}
  605: 
  606: 	return $r ;
  607: }
  608: 
  609: 
  610: # Changed by LG - added this subroutine for a shortcut call to to_utf8().
  611: # All it does is conversion of a string to utf8.
  612: sub 	str2utf8 {
  613: 	my 	($s, $e) = @_;
  614: 	my	$r = "" ;
  615: 
  616: 	$r .= to_utf8({ -string => $s, -charset => $e }) ;
  617: 	return $r ;
  618: }
  619: 
  620: sub	post_me2req {
  621: 	my	($me, $e, $hints) = @_ ;
  622: 	# Changed by LG - if no body found (may happen sometimes in multipart
  623: 	# messages) then attempt to grab the very first MIME part.  This is
  624: 	# somewhat hack-ish, but generally works ;-)
  625: 	# my	$mebh = $me->bodyhandle() or die "post_message(): no body?\n" ;
  626: 	my	$mebh = $me->bodyhandle() ;
  627: 	my	$mehh = $me->head() ;
  628:         if ( ! defined $mebh ) {
  629: 	    # Hack!  And get the corresponding header instead of overall one.
  630: 	    $mebh = $me->parts(0)->bodyhandle() or die "post_message(): no body?\n" ;
  631: 	    $mehh = $me->parts(0)->head() ;
  632: 	}
  633: 	my	$charset = $mehh->mime_attr("content-type.charset") || $e ;
  634: 	my	$subject = hdr2utf8($me->get('Subject') || "", $charset) ;
  635: 	chomp $subject ; 					# Changed by LG
  636: 	# Changed by LG.
  637: 	my $from = hdr2utf8($me->get('From') || "", $charset) ;
  638: 	chomp $from ;
  639: 	my $olddog_utf8 = str2utf8("\@", "ISO-8859-1") ;    # @ in utf
  640: 	my $newdog_utf8 = str2utf8($newdog, "ISO-8859-1") ; # obfuscated in utf
  641: 
  642: 	my	$hr = href2utf8(post_body2href($mebh->open("r")), $charset) ;
  643: 	my	$req = new HTTP::Request('POST', $post_uri) or
  644: 		die "new HTTP::Request(): $!\n" ;
  645: 
  646: 	if ($hints) {
  647: 	    my	$i ;
  648: 	    foreach $i (keys %$hints) {
  649: 		# Changed by LG - make hints override (not just complement)
  650: 		# existing values.
  651: 		# $hr->{$i} ||= $hints->{$i} ;
  652: 		$hr->{$i} = $hints->{$i} ;
  653: 	    }
  654: 	}
  655: 
  656: 	$hr->{subject} ||= $subject ;
  657: 	# Changed by LG - removed prefixing.
  658: 	# $hr->{subject} = "[mail2lj] " . $hr->{subject} ;
  659: 
  660: 
  661: 	# Changed by LG - added option to obfuscate all e-mail addresses in
  662: 	# the body of mail messages.
  663: 	if ( $opt_obfuscate ) {
  664: 	   $hr->{event} =~ 
  665:                s/\b([-+_.\w]+)($olddog_utf8)([-_.\w]+)\b/$1${newdog_utf8}$3/g ;
  666: 	}
  667: 
  668: 
  669: 	# Changed by LG - added options to add the plain or HTML-ized 'From'
  670: 	# field to the posted message.
  671: 	# 
  672: 	# NOTE: $from is already in UTF8, but the "From:" and HTML tags are
  673: 	# 	not.  Strictly speaking, everything that goes to $hr->{event}
  674: 	# 	MUST ALSO BE IN UTF8.  A cheating shortcut is possible:
  675: 	# 	since all lower ASCII characters are guaranteed to have
  676: 	# 	the same values in UTF8 as in plain ISO-8859-1, you could
  677: 	# 	possibly stick ASCII strings to $from without risk.  But in
  678: 	# 	order to add something non-ASCII, you absolutely MUST convert
  679: 	# 	it to UTF8 first!  To avoid the risk of forgetting this, the
  680: 	# 	following substitutions are done in a _proper_ (albeit 
  681: 	# 	somewhat awkward) way.
  682: 	if ( $opt_addfrom || $opt_addfromh ) {
  683: 
  684: 	   # Assemble the added From string in UTF8.
  685: 	   my $added_from ;
  686: 	   if ( $opt_addfrom ) {
  687: 	      $added_from = str2utf8("From: ", "ISO-8859-1") 
  688: 			    . $from . str2utf8("\n\n", "ISO-8859-1") ;
  689: 	   } elsif ( $opt_addfromh ) {
  690: 	      $added_from = str2utf8("<nobr><i><b>From:</b> ", "ISO-8859-1" )
  691: 			    . $from
  692: 			    . str2utf8("</i></nobr>\n\n", "ISO-8859-1") ;
  693: 	   }
  694: 
  695: 	   # This address is alway obfuscated (independently of the 
  696: 	   # '--obfuscate' option which only governs addresses _already_
  697: 	   # in the body.
  698: 	   $added_from =~ s/$olddog_utf8/$newdog_utf8/g ; 	# Obfuscate
  699: 	   $hr->{event} = $added_from . $hr->{event} ; 		# And append
  700: 	} 
  701: 
  702: 	# Changed by LG - added an option to preserve (html-ize) multiple
  703: 	# spaces and tabs (convert '\t' to eight '&nbsp;' and convert
  704: 	# multiple continuous spaces into sequence of ' &nbsp;').
  705: 	# Lines with tabs are additionally wrapped in <nobr>...</nobr> tags.
  706: 	#
  707: 	# NOTE: These tags should be in UTF8.  But since HTML tags themselves
  708: 	# 	are *certainly* in lower ASCII, we can safely stick them on
  709: 	# 	top of the existing UTF8 post.  But if you dare to add 
  710: 	# 	anything more than ASCII-markup, you'd better str2utf8() it
  711: 	# 	first!  See note in the $opt_addfrom/$opt_addfromh processing above.
  712: 	if ( $opt_keepspaces ) {
  713: 	   $hr->{event} =~ s/^(.*\t.*)$/<nobr>$1<\/nobr>/gm ;
  714: 	   $hr->{event} =~ s/\t/\&nbsp;\&nbsp;\&nbsp;\&nbsp;\&nbsp;\&nbsp;\&nbsp;\&nbsp;/g ;
  715: 	   $hr->{event} =~ s/  / \&nbsp;/g ;
  716: 	}
  717: 	
  718: 	#
  719: 	# Change by BV - added the option to put lj-cut after '--cut XX' lines
  720: 	#
  721: 	# Tweaked by LG - only adding lj-cut if more than $ljcut_delta lines
  722: 	# is left in the posting.  Also added $opt_ljcut_text.
  723: 	#
  724: 	if ($opt_ljcut>0) {
  725: 	    my $nlines = scalar( my @junk=split( /\n/, $hr->{event}, -1) ) - 1;
  726: 	    my $start=0;
  727: 	    for (my $i=0; $i<$opt_ljcut; $i++) {
  728: 		$start=index($hr->{event},"\n",$start)+1;
  729: 		if ($start == 0) {
  730: 		    last;
  731: 		}
  732: 	    }
  733: 	    # And insert the lj-cut if not too close to the end of the post.
  734: 	    if ($start>0 ) {
  735: 		if ( $nlines >= $opt_ljcut+$ljcut_delta ) {
  736: 		   my $ljcut = ( $opt_ljcut_text =~ /^\s*$/ ) ?
  737: 				'<lj-cut>' :
  738: 				'<lj-cut text="' . $opt_ljcut_text . '">' ;
  739: 		   substr($hr->{event}, $start,0) = $ljcut ;
  740: 		} else {
  741: 		   print STDERR "'--cut $opt_ljcut' requested, which is " .
  742: 				"within $ljcut_delta of the total $nlines " .
  743: 				"lines. Skipping lj-cut.\n" ;
  744: 		}
  745: 	    }
  746: 	}
  747: 
  748: 	$req->content_type('application/x-www-form-urlencoded');
  749: 	$req->content(href2string $hr) ;
  750: 
  751: 	print STDERR "working on request from $hr->{user}\n",
  752: 	      "From: $from\n", 					# Changed by LG
  753: 	      "Date: ", scalar localtime, "\n" ;
  754: 
  755: 	return $req ;
  756: }
  757: 
  758: sub	submit_request {
  759: 	my	($req, $proto) = @_ ;
  760: 	my	$ljres = {} ;
  761: 	my	$ua = new LWP::UserAgent or
  762: 		die "new LWP::UserAgent: $!\n" ;
  763: 	# Changed by LG - modified user-agent
  764: 	# $ua->agent("Mail2LJ/0.9");
  765: 	$ua->agent("Mail2LJ/${Version}${LGmod}");
  766: 	$ua->timeout(100);
  767: 	my	$res = $ua->request($req);
  768: 
  769: 	if ($proto && $proto eq "comment") {
  770: 	    if ($res->is_success) {
  771: 		$ljres->{'success'} = "OK";
  772: 	    } else {
  773: 		$ljres->{'success'} = "FAIL";
  774: 		$ljres->{'errmsg'} = "Client error: Error contacting server.";
  775: 	    }
  776: 
  777: 	    return $ljres ;
  778: 	}
  779: 
  780: 	if ($res->is_success) {
  781: 		%$ljres = split(/\n/, $res->content);
  782: 	} else {
  783: 		$ljres->{'success'} = "FAIL";
  784: 		$ljres->{'errmsg'} = "Client error: Error contacting server.";
  785: 	}
  786: 	return $ljres ;
  787: }
  788: 
  789: sub	ljcomment_form2string {
  790: 	my	$s = shift ;
  791: 	my	$h = {} ;
  792: 	my	$p = new HTML::TokeParser(\$s) or
  793: 	    die "new HTML::TokeParser(): $!\n" ;
  794: 	my	$token = $p->get_tag("form");
  795: 	die "get_inputs(): Wrong form.\n"
  796: 	    if ($token->[1]{action} ne $ljcomment_action) ;
  797: 
  798: 	while ($token = $p->get_tag("input") ) {
  799: 	    $h->{$token->[1]{name}} =
  800: 		$token->[1]{value} || '' if ($token->[1]{name});
  801: 	}
  802: 
  803: 	die "get_inputs(): Incomplete form data\n"
  804: 	    unless $h->{userpost} && $h->{journal} && $h->{parenttalkid} &&
  805: 		   $h->{itemid} && $h->{ecphash} ;
  806: 
  807: 	$h->{ecphash} =~ s/^ecph-// ;
  808: 
  809: 	return "$h->{userpost}-$h->{journal}-$h->{parenttalkid}-$h->{itemid}-$h->{ecphash}" ;
  810: }
  811: 
  812: sub	ljcomment_string2form {
  813: 	my	$s = shift ;
  814: 	my	$hr = {} ;
  815: 	my	$i ;
  816: 	my	@l = split /\-/, $s ;
  817: 
  818: 	foreach $i (qw/userpost journal parenttalkid itemid ecphash/) {
  819: 	    $hr->{$i} = shift @l ;
  820: 	}
  821: 
  822: 	die "badly formed formdata '$s'\n" unless $hr->{ecphash} ;
  823: 	$hr->{ecphash} = "ecph-" . $hr->{ecphash} ;
  824: 
  825: 	return $hr ;
  826: }
  827: 
  828: sub	normalize_header {
  829: 	my	($s, $e) = @_ ;
  830: 	my	$d = decode_mimewords($s) ;
  831: 	chomp $d ;
  832: 
  833: 	return encode_mimeword($d, 'B', $e) ;
  834: }
  835: 
  836: 
  837: sub	smstrip_data {
  838: 	my	$data = shift ;
  839: 	my	($hdr, $ftr) ;
  840: 	my	($who, $journal) ;
  841: 
  842: 	$data =~ /^(.+)Their reply was:(.+)You can view the discussion(.+)$/si
  843: 	    or return $data ;
  844: 	$hdr = $1 ;
  845: 	$data = $2 ;
  846: 	$ftr = $3 ;
  847: 
  848: 	$hdr =~ /\((\w+)\) replied to .* ((post)|(comment))/ and $who = $1 ;
  849: 
  850: 	$ftr =~ m,http://www\.livejournal\.com/talkpost.bml\?journal=(\w+),
  851: 	    and $journal = $1 ;
  852: 
  853: 	if ($who) {
  854: 	    $data = "user [$who] in [$journal]:\n" . $data ;
  855: 	}
  856: 
  857: 	$data =~ s/^\s+Subject:\s*$//m ;
  858: 	$data =~ s/^\s+Subject:\s(\S.*)\s*$/[$1]/m ;
  859: 	$data =~ s/\s+/ /gs ;
  860: 	$data =~ s/(.)/$tr{$1} || $1/ge ;
  861: 
  862: 	return $data ;
  863: }
  864: 
  865: sub	send_bounce {
  866: 	my	($errmsg, $orig, $charset) = @_ ;
  867: 
  868: 	# Changed by LG - use KOI-8 instead of Win-1251.
  869: 	# $charset ||= "windows-1251" ;
  870: 	$charset ||= "$MailCharset" ;
  871: 
  872: 	my	$bmsg = build MIME::Entity(
  873: 	    'From'         => "MAILER-DAEMON\@$host",
  874: 	    # Changed by LG - allow use of alternative addres for notifications.
  875: 	    # 'To'           => $orig->get('From'),
  876: 	    'To'           => $opt_bounces || $orig->get('From'),
  877: 	    'Subject'      => (
  878: 		"mail2lj failure (was: " . $orig->get('Subject') .  ")"
  879: 	    ),
  880: 	    'Content-Type' => "text/plain; charset=$charset" ,
  881: 	    'Data'         => <<EOF
  882: 
  883: 	Dear Mail2Lj User,
  884: 
  885:  Mail2Lj gateway at $host was trying hard to submit your request,
  886: but, unfortunately, to no avail: a silly, but fatal error has occured.
  887: Mail2Lj(tm) proudly presents the extremely informative error message:
  888: 
  889: '$errmsg'
  890: 
  891: Thank you for understanding,
  892: good luck next time,
  893: take care,
  894: sincerely, completely and, in general, very truly yours,
  895: -Mail2Lj.
  896: EOF
  897: 	);
  898: 	$bmsg->send("sendmail") ;
  899: 	$bmsg->purge() ;
  900: }
  901: 
  902: 
  903: sub print_usage {
  904:    # ----------------------------------------------------------------------- #
  905:    # print_usage( $Long );
  906:    #
  907:    # Prints help message.  If defined $Long, the message is more detailed
  908:    # as opposed to default brief description.
  909:    # ----------------------------------------------------------------------- #
  910:    my ( $long ) = @_; 			# Were we called with a parameter?
  911: 
  912:    my $spacer = ' ' x length($shortname); 	# bunch of spaces
  913: 
  914:    # ---------------------------------------------------------------------
  915:    # Short usage will always be printed when called.
  916:    # Indentation messed up because of the HERE-document.
  917:    # ---------------------------------------------------------------------
  918:    print <<___END_SHORT;
  919: $shortname v. ${Version}     by jason\@nichego.net (http://jsn.livejournal.com).
  920: Tweaked to v. ${Version}${LGmod}  by Lev Gorenstein \<lev\@ledorub.poxod.com\>, 2007.
  921: 
  922: Usage:
  923:      $shortname  ACTION [options] < InputFile
  924:      cat MailMessage | $shortname  ACTION [options]
  925: 
  926: A script to post incoming mail messages to Livejournal.com journals.
  927: Reads STDIN and connects to Livejournal's HTTP posting interface.
  928: 
  929: This is a modification of mail2lj.pl script by Jason 
  930: (http://jsn.livejournal.com) described at http://mail2lj.nichego.net/.
  931: I added command line processing and couple more tweaks.
  932: 
  933: Distributed freely under GNU Public License with absolutely no warranty.
  934: 
  935: ___END_SHORT
  936: 
  937: 
  938:    # ---------------------------------------------------------------------
  939:    # When called in a long format, usage should be followed by some more info.
  940:    # Indentation messed up because of the HERE-document.
  941:    # ---------------------------------------------------------------------
  942:    if ( defined $long && $long !~ /^\s*short\s*$/i ) {
  943:       print <<______END_HELP;
  944: ACTIONS:
  945: post           Original script used this to handle messages that had keywords
  946:                inside (see http://mail2lj.nichego.net/userguide.html) and 
  947:                used 'post-...' and 'hpost-...' to post keywordless messages
  948:                directly.  This version doesn't require keywords (i.e. 'post'
  949:                can handle keywordless messages and everything can be set via
  950:                command line), but if you DO use keywords, then use this action.
  951: 
  952: post-(user)-(password)
  953:                A direct post of mail message (without looking for keywords in
  954:                the body) using whatever settings supplied on the command line.
  955:                With proper command line parameters, username and password can
  956:                be completely bogus (i.e. 'post-aa-bb -u RealUser -p RealPass').
  957: 
  958: hpost-(user)-(MD5Hash_of_password)
  959:                A direct post of mail message (without looking for keywords in
  960:                the body) using whatever settings supplied on the command line,
  961:                Same as 'post-...', but uses a password hash instead of 
  962:                clear-text password.
  963:                With proper command line parameters, username and hash can be
  964:                completely bogus (i.e. 'hpost-aa-bb -u RealUser --hp RealHash').
  965: 
  966: 
  967: Options:
  968: -u USER, --user USER
  969:                Use this LiveJournal user name to login.
  970: 
  971: -p PASS, --password PASS
  972:                Use this LiveJournal password to login.  Use of this option
  973:                is deprecated because of clear-text password.
  974: 
  975: -hp MD5Hash, --hpassword MD5Hash
  976:                Use this MD5 hash of the password to login.  To generate a hash,
  977:                do this:
  978: 			perl -MDigest::MD5 \
  979: 			     -e 'print Digest::MD5::md5_hex("PASSWORD")."\\n"'
  980: 
  981: -j JOURNAL, --usejournal JOURNAL
  982:                When posting to the community (or the journal that's different
  983:                from the one you've specified via '--user'), use this option 
  984:                to specify that community's name.  E.g. if the user
  985:                'gusarskie_vesti' wants to post to community 'gusary', it can
  986:                be done with options like this:
  987: 			post -u gusarskie_vesti -p PASS --usejournal gusary
  988: 
  989: -s SUBJECT, --subject SUBJECT
  990:                Use this subject for the posting.  If absent, defaults to 
  991:                e-mail's Subject:.
  992: 
  993: -t TAGLIST, --tags TAGLIST
  994:                Use tags from TAGLIST for posted message.  Within a tag list,
  995:                tags should be separated by commas.  If your tags contain
  996:                special characters or spaces, make sure to enclose TAGLIST in
  997:                single or double quotes to protect from the shell.  Multiple
  998:                '-t' options are allowed and taglists will be combined.
  999: 
 1000: --notaglist, --notags
 1001:                Unsets all previously defined tags.  Thus, a call to 
 1002:                   $shortname ... --tags X --tags Y ... --notags --tags Z
 1003:                will yield a taglist consisting of just "Z".  This option is
 1004:                rarely needed and added only for the sake of completeness.
 1005: 
 1006: -d DATE, --date DATE
 1007:                Label posting with this date.  Date should be in LiveJournal's
 1008:                format: DD.MM.YYYY HH:mm.  If absent, current date/time is used.
 1009: 
 1010: --backdated
 1011:                If set, tells LiveJournal to make this message back-dated 
 1012:                (i.e. to set 'Date out-of-order' flag to prevent this item
 1013:                from showing in people's friends lists).  Note that currently
 1014:                Livejournal only allows back-dated entries in individual
 1015:                journals (not in communities), so use with caution.  The option
 1016:                can be negated ('--nobackdated').  Default is '--nobackdated'.
 1017: 
 1018: --security public|protected|private
 1019:                Post security mode.  Default is "public".
 1020: 
 1021: -f, --formatted
 1022:                If set, tells LiveJournal to assume our message to be already
 1023:                formatted (i.e. '--formatted' turns OFF LJ's autoformat
 1024:                feature).  The option can be negated ('--noformatted').
 1025:                Default is '--noformatted' (i.e. *use* LJ's autoformat).
 1026: 
 1027: --mood MOOD    Current Mood for Livejournal.  TEXT ONLY (images not supported).
 1028:                Defaults to nothing.
 1029: 
 1030: --music MUSIC  Current Music for Livejournal.  Defaults to nothing.
 1031: 
 1032: --picture KEYWORD, --userpic KEYWORD
 1033:                Keyword for the Livejournal userpic to use.  Default one is
 1034:                used when not specified.
 1035: 
 1036: -c on|yes|default|off|no|noemail, --comments on|yes|default|off|no|noemail
 1037:                Controls permissions to leave comments for this post.  
 1038:                "on" ("yes", "default") will use the journal's default settings.
 1039:                "off" or "no" prohibit comments.  "noemail" allows comments,
 1040:                but tells Livejournal not to email them to you.
 1041: 
 1042: --from, --addfrom
 1043:                Insert the From: field from the e-mail as the first line of 
 1044:                the posted message.  The field is added in plain text (without
 1045:                any HTML-formatting - see '--fromh' for that).  For slight 
 1046:                antispam protection, '\@' is replaced by '$newdog'.  The option 
 1047:                can be negated ('--nofrom').  Default is '--nofrom'.
 1048:                Note: this option is independent from '--obfuscate' (i.e. the
 1049:                prepended From is always obfuscated, even if the rest of the 
 1050:                message is not).
 1051: 
 1052: --fromh, --addfromh
 1053:                Same as '--from', but uses HTML-markup to highlight inserted 
 1054:                field (<nobr><i><b>From:</b> Address</i></nobr>).  This is 
 1055:                nice for mailing list -> Livejournal crossposting.  The option
 1056:                can be negated ('--nofromh').  Default is '--nofromh'.
 1057: 
 1058: -o, --obfuscate
 1059:                Obfuscate all e-mail addresses that are present in the body
 1060:                of the message.  For slight antispam protection, '\@' in these
 1061:                addresses is replaced by '$newdog'.  The option can be negated
 1062:                ('--noobfuscate').  Default is '--noobfuscate'.
 1063: 
 1064: --spaces, --keepspaces
 1065:                Normally the script does not change original message text,
 1066:                and all of it is preserved in the body of resulting LJ post.
 1067:                Which means that all tabs and multiple consecutive spaces 
 1068:                (while valid in e-mail and preserved in the post), will not
 1069:                be properly *shown* in the browser (browser will display them
 1070:                as single space).  With '--spaces', however, all tabs will
 1071:                be converted to 8 '\&nbsp;' instances, and each pair of 
 1072:                consecutive spaces will be converted to a ' \&nbsp' sequence.
 1073:                Additionally, lines with tabs will be wrapped in <nobr> tag.
 1074:                This way the formatting of original e-mail will be much 
 1075:                better preserved in the journal.  The option can be negated
 1076:                ('--nospaces').  Default is '--nospaces'.
 1077: 
 1078: --ljcut NUM, --cut NUM, -l NUM
 1079:                Inserts '<lj-cut>' after NUM lines of the post content.
 1080:                If the resulting lj-cut happens to be within $ljcut_delta lines from
 1081:                the end of the post, the cut will not be added.
 1082: 
 1083: --ljcut-text TEXT, --cut-text TEXT, --cuttext TEXT
 1084:                Text to use as lj-cut text parameter (in <lj-cut text="TEXT">).
 1085:                If the text contains nothing but whitespace, it is ignored.
 1086:                Remember to quote spaces and special characters from the shell.
 1087: 
 1088: --charset CHARSET
 1089:                This option tells the script that all COMMAND LINE options are
 1090:                given in this charset.  Default is "$SystemCharset".
 1091:                Remember, THIS HAS NOTHING TO DO with the __posting's charset__
 1092:                (which is determined from email headers and then converted to
 1093:                utf8).  It also has absolutely no effect on the in-the-body
 1094:                keywords (they are also governed by email's charset).  This
 1095:                option is meaningful ONLY for the text that you supply VIA
 1096:                COMMAND LINE (e.g. '-s Subject' or '--cuttext TEXT').
 1097:                
 1098: -b xxx\@yyy, --bounces xxx\@yyy
 1099:                Normally, if errors occur during posting (e.g. wrong password),
 1100:                the script sends an error notification to the _original poster_
 1101:                (i.e., the address in the original From: field).  This makes
 1102:                perfect sense for multi-user installations.  But occasionally 
 1103:                there is a need to send all errors to a single _maintainer_ 
 1104:                (e.g., if you use the script as a mailing list --> LiveJournal
 1105:                gateway).  This option allows exactly that.  Default is unset
 1106:                (i.e. errors go to original poster).
 1107: 
 1108:  -h, --help:  This help.
 1109: 
 1110: 
 1111: If you decide to use keywords in the body of the message (as opposed to 
 1112: command line options), they should look like this:
 1113: 
 1114: 	From: ....		\\
 1115: 	To: ....		 + 	# Regular e-mail headers
 1116: 	Subject: ...		/
 1117: 					# Normal blank line after headers
 1118: 	User: gusarskie_vesti
 1119: 	Password: password		# (or Hpassword: MD5Hash)
 1120: 	Date: 22.01.2007 5:04
 1121: 	Security: private
 1122: 	Subject: Rzhevskij zhiv!
 1123: 	Tags:  Junk, Viva Rzhevskij!
 1124: 	Notags: yes 			# Clears all preceding tags
 1125: 	Formatted: on 			# Or equivalent "Autoformat: off"
 1126: 	Usejournal: gusary
 1127: 	Mood: okay
 1128: 	Music: silence
 1129: 	Backdated: yes
 1130: 	Comments: no
 1131: 					# Blank line
 1132: 	Oh well. some text 		# Text of your message.
 1133: 
 1134: And the text would be posted.
 1135: 
 1136: Almost all keyword fields (as well as their command line counterparts)
 1137: are optional and have reasonable defaults.  The only mandatory parameter
 1138: is the user name (well, doh!).  See more on keywords in the original
 1139: script's user guide: http://mail2lj.nichego.net/userguide.html
 1140: 
 1141: ______END_HELP
 1142:       print "\n";
 1143:    }  # End of "if $long" test
 1144: 
 1145:    # ---------------------------------------------------------------------
 1146:    # All done
 1147:    # ---------------------------------------------------------------------
 1148: 
 1149:     return;
 1150: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>