| 1 | #!/usr/bin/env ruby |
|---|
| 2 | $KCODE= 'e' |
|---|
| 3 | # |
|---|
| 4 | # posttdiary: update tDiary via e-mail. $Revision: 1.5 $ |
|---|
| 5 | # |
|---|
| 6 | # Copyright (C) 2002, All right reserved by TADA Tadashi <sho@spc.gr.jp> |
|---|
| 7 | # You can redistribute it and/or modify it under GPL2. |
|---|
| 8 | # |
|---|
| 9 | |
|---|
| 10 | def usage |
|---|
| 11 | <<-TEXT.gsub( /^\t{2}/, '' ) |
|---|
| 12 | #{File::basename __FILE__}: update tDiary via e-mail. |
|---|
| 13 | usage: ruby #{File::basename __FILE__} [options] <url> [user] [passwd] |
|---|
| 14 | arguments: |
|---|
| 15 | url: update.rb's URL of your diary. |
|---|
| 16 | user: user ID of your diary updating. |
|---|
| 17 | passwd: password of your diary updating. |
|---|
| 18 | If To: field of the mail likes "user-passwd@example.com", |
|---|
| 19 | you can omit user and passwd arguments. |
|---|
| 20 | options: |
|---|
| 21 | --image-path, -i: directory of image saving into. |
|---|
| 22 | --image-url, -u: URL of image. |
|---|
| 23 | You have to specify both options when using images. |
|---|
| 24 | --image-format, -f: format of image tag specified image serial |
|---|
| 25 | number as '$0' and image url as '$1'. |
|---|
| 26 | default format is ' <img class="photo" src="$1" alt="">'. |
|---|
| 27 | --use-subject, -s: use mail subject to subtitle. |
|---|
| 28 | and insert image between subtitle and body. |
|---|
| 29 | TEXT |
|---|
| 30 | end |
|---|
| 31 | |
|---|
| 32 | def image_list( date, path ) |
|---|
| 33 | image_path = [] |
|---|
| 34 | Dir.foreach( path ) do |file| |
|---|
| 35 | if file =~ /(\d{8,})_(\d+)\./ and $1 == date then |
|---|
| 36 | image_path[$2.to_i] = file |
|---|
| 37 | end |
|---|
| 38 | end |
|---|
| 39 | image_path |
|---|
| 40 | end |
|---|
| 41 | |
|---|
| 42 | def bmp_to_png( bmp ) |
|---|
| 43 | png = bmp.sub( /\.bmp$/, '.png' ) |
|---|
| 44 | begin |
|---|
| 45 | require 'magick' |
|---|
| 46 | img = Magick::Image::new( bmp ) |
|---|
| 47 | img.write( 'magick' => 'png', 'filename' => png ) |
|---|
| 48 | rescue LoadError |
|---|
| 49 | system( "convert #{bmp} #{png}" ) |
|---|
| 50 | end |
|---|
| 51 | if FileTest::exist?( png ) |
|---|
| 52 | File::delete( bmp ) |
|---|
| 53 | png |
|---|
| 54 | else |
|---|
| 55 | bmp |
|---|
| 56 | end |
|---|
| 57 | end |
|---|
| 58 | |
|---|
| 59 | begin |
|---|
| 60 | |
|---|
| 61 | raise usage if ARGV.length < 1 |
|---|
| 62 | |
|---|
| 63 | require 'getoptlong' |
|---|
| 64 | parser = GetoptLong::new |
|---|
| 65 | image_dir = nil |
|---|
| 66 | image_url = nil |
|---|
| 67 | image_format = ' <img class="photo" src="$1" alt="">' |
|---|
| 68 | use_subject = false |
|---|
| 69 | parser.set_options( |
|---|
| 70 | ['--image-path', '-i', GetoptLong::REQUIRED_ARGUMENT], |
|---|
| 71 | ['--image-url', '-u', GetoptLong::REQUIRED_ARGUMENT], |
|---|
| 72 | ['--image-format', '-f', GetoptLong::REQUIRED_ARGUMENT], |
|---|
| 73 | ['--use-subject', '-s', GetoptLong::NO_ARGUMENT] |
|---|
| 74 | ) |
|---|
| 75 | begin |
|---|
| 76 | parser.each do |opt, arg| |
|---|
| 77 | case opt |
|---|
| 78 | when '--image-path' |
|---|
| 79 | image_dir = arg |
|---|
| 80 | when '--image-url' |
|---|
| 81 | image_url = arg |
|---|
| 82 | when '--image-format' |
|---|
| 83 | image_format = arg |
|---|
| 84 | when '--use-subject' |
|---|
| 85 | use_subject = true |
|---|
| 86 | end |
|---|
| 87 | end |
|---|
| 88 | rescue |
|---|
| 89 | raise usage |
|---|
| 90 | end |
|---|
| 91 | raise usage if (image_dir and not image_url) or (not image_dir and image_url) |
|---|
| 92 | image_dir.sub!( %r[/*$], '/' ) if image_dir |
|---|
| 93 | image_url.sub!( %r[/*$], '/' ) if image_url |
|---|
| 94 | url = ARGV.shift |
|---|
| 95 | if %r|http://([^:/]+)(?::(\d+))?(/.*)| =~ url then |
|---|
| 96 | host = $1 |
|---|
| 97 | port = ($2 || 80).to_i |
|---|
| 98 | cgi = $3 |
|---|
| 99 | else |
|---|
| 100 | raise 'bad url.' |
|---|
| 101 | end |
|---|
| 102 | |
|---|
| 103 | user = ARGV.shift |
|---|
| 104 | pass = ARGV.shift |
|---|
| 105 | |
|---|
| 106 | require 'base64' |
|---|
| 107 | require 'nkf' |
|---|
| 108 | image_name = nil |
|---|
| 109 | |
|---|
| 110 | mail = NKF::nkf( '-m0 -Xed', ARGF.read ) |
|---|
| 111 | raise "#{File::basename __FILE__}: no mail text." if not mail or mail.length == 0 |
|---|
| 112 | |
|---|
| 113 | head, body = mail.split( /(?:\r?\n){2}/, 2 ) |
|---|
| 114 | |
|---|
| 115 | if head =~ /Content-Type:\s*Multipart\/Mixed.*boundary=\"(.*?)\"/im then |
|---|
| 116 | if not image_dir or not image_url then |
|---|
| 117 | raise "no --image-path and --image-url options" |
|---|
| 118 | end |
|---|
| 119 | |
|---|
| 120 | bound = "--" + $1 |
|---|
| 121 | body_sub = body.split( Regexp.compile( Regexp.escape( bound ) ) ) |
|---|
| 122 | body_sub.each do |b| |
|---|
| 123 | sub_head, sub_body = b.split( /(?:\r?\n){2}/, 2 ) |
|---|
| 124 | |
|---|
| 125 | next unless sub_head =~ /Content-Type:/i |
|---|
| 126 | |
|---|
| 127 | if sub_head =~ %r[^Content-Type:\s*text/plain]i then |
|---|
| 128 | @body = sub_body |
|---|
| 129 | elsif sub_head =~ %r[ |
|---|
| 130 | ^Content-Type:\s* |
|---|
| 131 | (?:image/ | application/octet-stream).+ |
|---|
| 132 | name=".+(\.[^.]+?)" (?# 1: extension) |
|---|
| 133 | ]imx |
|---|
| 134 | image_ext = $1.downcase |
|---|
| 135 | now = Time::now |
|---|
| 136 | list = image_list( now.strftime( "%Y%m%d" ), image_dir ) |
|---|
| 137 | image_name = now.strftime( "%Y%m%d" ) + "_" + list.length.to_s + image_ext |
|---|
| 138 | File::umask( 022 ) |
|---|
| 139 | open( image_dir + image_name, "wb" ) do |s| |
|---|
| 140 | begin |
|---|
| 141 | s.print Base64::decode64( sub_body.strip ) |
|---|
| 142 | rescue NameError |
|---|
| 143 | s.print decode64( sub_body.strip ) |
|---|
| 144 | end |
|---|
| 145 | end |
|---|
| 146 | if /\.bmp$/i =~ image_name then |
|---|
| 147 | bmp_to_png( image_dir + image_name ) |
|---|
| 148 | image_name.sub!( /\.bmp$/i, '.png' ) |
|---|
| 149 | end |
|---|
| 150 | @image_name ||= [] |
|---|
| 151 | @image_name << image_name |
|---|
| 152 | end |
|---|
| 153 | end |
|---|
| 154 | elsif head =~ /^Content-Type:\s*text\/plain/i |
|---|
| 155 | @body = body |
|---|
| 156 | else |
|---|
| 157 | raise "cannot read this mail" |
|---|
| 158 | end |
|---|
| 159 | |
|---|
| 160 | if @image_name then |
|---|
| 161 | img_src = "" |
|---|
| 162 | @image_name.each do |i| |
|---|
| 163 | serial = i.sub( /^\d+_(\d+)\./n, '\1' ) |
|---|
| 164 | img_src += image_format.gsub( /\$0/, serial ).gsub( /\$1/, image_url + i ) |
|---|
| 165 | end |
|---|
| 166 | if use_subject then |
|---|
| 167 | @body = "#{img_src}\n#{@body}".sub( /(?:\r?\n|\r)+\z/, "\n" ) |
|---|
| 168 | else |
|---|
| 169 | @body = "#{@body}".sub( /(?:\r?\n|\r)+\z/, "\n" ) << img_src |
|---|
| 170 | end |
|---|
| 171 | end |
|---|
| 172 | |
|---|
| 173 | addr = nil |
|---|
| 174 | if /^To:(.*)$/ =~ head then |
|---|
| 175 | addr = case to = $1.strip |
|---|
| 176 | when /.*?\s*<(.+)>/, /(.+?)\s*\(.*\)/ |
|---|
| 177 | $1 |
|---|
| 178 | else |
|---|
| 179 | to |
|---|
| 180 | end |
|---|
| 181 | end |
|---|
| 182 | |
|---|
| 183 | if /([^-]+)-(.*)@/ =~ addr then |
|---|
| 184 | user ||= $1 |
|---|
| 185 | pass ||= $2 |
|---|
| 186 | end |
|---|
| 187 | |
|---|
| 188 | raise "no user." unless user |
|---|
| 189 | raise "no passwd." unless pass |
|---|
| 190 | |
|---|
| 191 | subject = '' |
|---|
| 192 | nextline = false |
|---|
| 193 | headlines = head.split( /(?:\r?\n|\r)+/ ) |
|---|
| 194 | for n in 0 .. headlines.size-1 |
|---|
| 195 | if nextline then |
|---|
| 196 | if /^[ \t]/ =~ headlines[n] then |
|---|
| 197 | s = headlines[n].sub( /^[ \t]/, '' ) |
|---|
| 198 | subject += NKF::nkf( '-eXd', s ) |
|---|
| 199 | else |
|---|
| 200 | break |
|---|
| 201 | end |
|---|
| 202 | end |
|---|
| 203 | if /^Subject:\s*(.+)$/i =~ headlines[n] then |
|---|
| 204 | subject = NKF::nkf( '-eXd', $1 ) |
|---|
| 205 | nextline = true |
|---|
| 206 | end |
|---|
| 207 | end |
|---|
| 208 | if use_subject then |
|---|
| 209 | title = '' |
|---|
| 210 | @body = "#{subject}\n#{@body}" |
|---|
| 211 | else |
|---|
| 212 | title = subject |
|---|
| 213 | end |
|---|
| 214 | |
|---|
| 215 | require 'cgi' |
|---|
| 216 | require 'nkf' |
|---|
| 217 | data = "title=#{CGI::escape title}" |
|---|
| 218 | data << "&body=#{CGI::escape @body}" |
|---|
| 219 | data << "&append=true" |
|---|
| 220 | |
|---|
| 221 | require 'net/http' |
|---|
| 222 | Net::HTTP.start( host, port ) do |http| |
|---|
| 223 | auth = ["#{user}:#{pass}"].pack( 'm' ).strip |
|---|
| 224 | res, = http.get( cgi, { |
|---|
| 225 | 'Authorization' => "Basic #{auth}", |
|---|
| 226 | 'Referer' => url }) |
|---|
| 227 | if %r|<input type="hidden" name="csrf_protection_key" value="([^"]+)">| =~ res.body then |
|---|
| 228 | data << "&csrf_protection_key=#{CGI::escape( CGI::unescapeHTML( $1 ) )}" |
|---|
| 229 | end |
|---|
| 230 | res, = http.post( cgi, data, { |
|---|
| 231 | 'Authorization' => "Basic #{auth}", |
|---|
| 232 | 'Referer' => url }) |
|---|
| 233 | end |
|---|
| 234 | |
|---|
| 235 | rescue |
|---|
| 236 | $stderr.puts $! |
|---|
| 237 | $stderr.puts $@.join( "\n" ) |
|---|
| 238 | File::delete( image_dir + image_name ) if image_dir and image_name and FileTest::exist?( image_dir + image_name ) |
|---|
| 239 | exit 1 |
|---|
| 240 | end |
|---|