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