「ただダラ」Ruby版
一応出来たので貼っておきます。Ruby1.8以降専用になっちゃいましたが…。こんなもんでどうすかねぇ?
id:jounoさんのコメントを見て、そのようにも使えるように機能追加も行ってみました。ありがとうございます。
追加機能の詳しい内容は、あちらの日記を参照してください。
「はてダラ」から、こんなところまで来ちゃいましたねぇ…。
#!/usr/bin/env ruby =begin hw.rb - tDiary Writer. Copyright (C) 2004 by Hahahaha. <rin_ne@big.or.jp> http://www20.big.or.jp/~rin_ne/ This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =end =begin # # Configure file example. id:yourid password:yourpassword cgi_url:http://example.com/tdiary/update.rb # txt_dir:/usr/yourid/diary # touch:/usr/yourid/diary/hw.touch # proxy:http://www.example.com:8080/ # g:yourgroup # client_encoding:Shift_JIS # server_encoding:UTF-8 ## for Unix, if Encode module is not available. # filter:iconv -f euc-jp -t utf-8 %s =end require 'optparse' require 'uri' require 'iconv' require 'stringio' require 'tempfile' require 'net/http' Net::HTTP.version_1_2 Version = '0.1.0' ProgramName = 'tDiary Writer' Banner = "#{ProgramName} Version #{Version}\nCopyright (c) 2004 by Hahahaha.\n" DefaultAgent = "#{ProgramName.tr(' ', '')}/#{Version}" DATE_FORMAT = Regexp.new('(\d{4})-(\d\d)-(\d\d)') CONF_FORMAT = Regexp.new('(.*?):(.*)$') module MessagePrint @@debug = false ## Debug flag on. def debug_on @@debug = true end def debug return @@debug end ## Debug print. def print_debug(str) $stdout.puts("DEBUG: #{str}") if @@debug end ## Error Exit. def error_exit(str) $stderr.puts("Error: #{str}") exit(1) end ## Print message. def print_message(str) $stdout.puts(str) end end # Configureuration store class. # class Configure include MessagePrint attr_accessor :user # tDiary admin user id attr_accessor :password # tDiary admin password attr_accessor :filter_command # Filter command. attr_accessor :config_file # Configure file. attr_accessor :touch_file # Touch file. attr_accessor :target_files # Target files. attr_accessor :target_file # Target file. attr_accessor :target_date # Target date. attr_accessor :client_encoding # Client encoding. attr_accessor :server_encoding # Server encoding. attr_accessor :agent # Agent name. attr_accessor :timeout # Timeout. attr :txt_dir # Directory for "YYYY-MM-DD.txt". attr :proxy # Proxy location. attr :location # Your tDiary update CGI URL. attr :delay # Delay localtime. attr_accessor :force_update # Force update flag. attr_accessor :mode # Action mode. def initialize @user = nil @password = nil @config_file = 'config.txt' @touch_file = 'touch.txt' @target_files = [] @target_file = nil @target_date = nil @client_encoding = 'EUC-JP' @server_encoding = 'EUC-JP' @agent = "#{ProgramName.tr(' ', '')}/#{Version}" @timeout = 180 @txt_dir = '.' @proxy = nil @location = nil @filter_command = nil @delay = 0 @force_update = false @mode = 'replace' end # Load configuration file. # def load print_debug("Loading config file (#{@config_file}).") begin File.open(@config_file) {|f| f.each {|line| next if /^#/.match(line) m = CONF_FORMAT.match(line) next unless m[1].strip opt = m[1].strip.downcase val = m[2].strip val = nil if val.empty? case opt when "id" @user = val unless @user print_debug("Configure#load: id:#{@user}") when "password" @password = val unless @password print_debug("Configure#load: password:#{@password}") when "client_encoding" @client_encoding = val print_debug("Configure#load: client_encoding:#{@client_encoding}") when "server_encoding" @server_encoding = val print_debug("Configure#load: server_encoding:#{@server_encoding}") when "filter" @filter_command = val print_debug("Configure#load: filter:#{@filter_command}") when "touch" @touch_file = val print_debug("Configure#load: touch:#{@touch}") when "cgi_url" @location = URI.parse(val).normalize if ((not @location.host) or (not @location.port)) raise("cgi_url: Invalid URI: '#{val}'") end print_debug("Configure#load: cgi_url:#{val}") when "proxy" @proxy = URI.parse(val).normalize if ((not @proxy.host) or (not @proxy.port)) raise("proxy: Invalid URI: '#{val}'") end print_debug("Configure#load: proxy:#{val}") when "txt_dir" unless File.directory?(val) raise("txt_dir: No such directory: '#{val}'") end @txt_dir = val print_debug("Configure#load: txt_dir:#{@txt_dir}") when "delay" @delay = val.to_i * 3600 print_debug("Configure#load: delay:#{val}") else raise("#{opt}: No such option.") end } } @proxy = URI.parse('') unless @proxy raise 'cgi_url not found' unless @location rescue error_exit("[Configure#load] #$! in '#{@config_file}'.") end end end # tDiary Access class. # class TDiaryServer include MessagePrint attr :response def initialize(config) raise(ArgumentError, config.to_s) if config.class != Configure @request = nil @http = nil @response = nil @conf = config create_http end # Open server. # def open(method = 'get') return if @request case method.strip.downcase when 'get' @request = Net::HTTP::Get.new(@conf.location.path) when 'post' @request = Net::HTTP::Post.new(@conf.location.path) when 'head' @request = Net::HTTP::Head.new(@conf.location.path) when 'put' @request = Net::HTTP::Put.new(@conf.location.path) end @request['User-Agent'] = @conf.agent user = @conf.user pass = @conf.password unless (user) print "Username: " user = $stdin.gets.chomp end unless (pass) print "Password: " pass = $stdin.gets.chomp end @request.basic_auth(user, pass) end # Close server. # def close @request = nil end # Send request. # def request(body = nil) open unless @request if (body.class == Hash) body_ary = [] body.each_pair {|name, val| body_ary.push( [name, val].join('=') ) } body = body_ary.join(';') end @request['Content-Type'] = 'application/x-www-form-urlencoded' 2.times {|i| begin @http.start {|http| @response = http.request(@request, body) } rescue if (@response) case @response.code when 401 error_exit('[TDiaryServer#request]: Check username/password.') when 407 error_exit('[TDiaryServer#request]: Check username/password.') else print_debug('[TDiaryServer#request]: RETRY.') print_message('Retry.') next end end raise('[TDiaryServer#request]: Network error. Check cgi_url, proxy, timeout and so on.') end break } if (@response.code.to_i != 200) error_exit("[TDiaryServer#request]: ErrorCode = #{@response.code}.") end end def send_entry( title, body ) open unless @request year, month, day = DATE_FORMAT.match(@conf.target_date).captures self.request({ @conf.mode => @conf.mode, 'old' => year + month + day, 'year' => year.to_i.to_s, 'month' => month.to_i.to_s, 'day' => day.to_i.to_s, 'title' => URI.escape(title, /./), 'body' => URI.escape(body, /./) }) end # Create HTTP instance. # def create_http p_user, p_pass = @conf.proxy.userinfo.split(':') if @conf.proxy.userinfo print_debug("use proxy: #{@conf.proxy.normalize}") if @conf.proxy.host @http = Net::HTTP.new(@conf.location.host, @conf.location.port, @conf.proxy.host, @conf.proxy.port, p_user, p_pass) @http.read_timeout = @conf.timeout end private :create_http end # Main routine. # class MainRoutine include MessagePrint def initialize @conf = nil @server = nil end def run @conf = Configure.new # Parse ARGV. ARGV.options {|opt| begin opt.banner = Banner + "\n" + "#{opt.banner}" opt.summary_width = 16 opt.on( '-d', "Debug. Use this switch for verbose log.") { unless debug debug_on print_debug("Debug flag on.") print_message(Banner) end } opt.on( '-u username', "Username. Specify username.") {|arg| @conf.user = arg } opt.on( '-p password', "Password. Specify password.") {|arg| @conf.password = arg } opt.on( '-a agent', "User agent. Default value is '#{DefaultAgent}'.") {|arg| @conf.agent = arg } opt.on( '-T seconds', "Timeout. Default value is #{@conf.timeout}.") {|arg| raise("-T #{arg}: not integer.") if /\d+/ !~ arg @conf.timeout = arg.to_i } opt.on( '-f filename', "File. Send only this file without checking timestamp.") {|arg| if (DATE_FORMAT !~ File.basename(arg, ".txt")) raise("-f #{arg}: Invalid filename format.") end raise("-f #{arg}: No such file.") unless File.file?(arg) @conf.target_files.push(arg) @conf.force_update = true } opt.on( '-F filename', "Nearly -f option, but only one file with arbitrary filename.") {|arg| raise("-F #{arg}: No such file.") unless File.file?(arg) @conf.target_file = arg @conf.force_update = true } opt.on( '-s [yyyy-mm-dd]', "STDIN or -F filename is treated as 'yyyy-mm-dd' or current date.") {|arg| if arg raise("-s #{arg}: Invalid date format.") if DATE_FORMAT !~ arg @conf.target_date = arg else @conf.target_date = Time.now.localtime @conf.mode = 'append' end @conf.force_update = true } opt.on( '-n config_file', "Config file. Default value is '#{@conf.config_file}'.") {|arg| raise("-n #{arg}: No such file.") unless FileTest.file?(arg) @conf.config_file = arg } opt.parse! rescue error_exit($!) end } if (@conf.target_file) error_exit('Set -s option when -F set.') unless @conf.target_date end @conf.load if (@conf.target_date) @conf.target_date = (@conf.target_date - @conf.delay).strftime('%Y-%m-%d') end @server = TDiaryServer.new(@conf) count = 0 files = setup_file_list if (files.empty? and @conf.target_date) title, body = read_title_body @server.open('post') print_message("Post #{mode}:#{@conf.target_date}") @server.send_entry( title, body ) print_message('Post OK.') count = 1 else files.each {|file| @conf.target_date = File.basename(file.downcase, '.txt') unless @conf.target_date title, body = read_title_body(file) @server.open('post') print_message("Post replace:#{@conf.target_date}") @server.send_entry( title, body ) print_message('Post OK.') sleep(1) count += 1 } end @server.close if (count==0) print_message('No files are posted.') else unless (@conf.force_update) begin File.open(@conf.touch_file, 'w') {|file| file.puts(get_timestamp) } rescue error_exit("[MainRoutine#run] #$!") end end end end # Setup file list. def setup_file_list files = [] if (@conf.target_file) files.push(@conf.target_file) print_debug("MainRoutine#setup_file_list: option -F: #{files.join(', ')}") elsif (not @conf.target_files.empty?) files.concat(@conf.target_files) print_debug("MainRoutine#setup_file_list: option -f: #{files.join(', ')}") end if (files.empty?) Dir.glob("#{@conf.txt_dir}/*.txt") {|file| next if ( File.file?(@conf.touch_file) and (File.mtime(file) < File.mtime(@conf.touch_file)) ) next if DATE_FORMAT !~ File.basename(file.downcase, ".txt") files.push(file) } print_debug("MainRoutine#setup_file_list: " + "current dir (#{@conf.txt_dir}) : #{files.join(', ')}") end return files end # Read title and body. def read_title_body(file = nil) input = nil begin # Read file. if (file) input = StringIO.new(File.open(file).read) print_debug("read_title_body: input: #{file}."); else input = StringIO.new($stdin.read) print_debug("read_title_body: input: STDIN."); end # Execute filter command, if any. if (@conf.filter_command) tmpfile = Tempfile.open($0) tmpfile.write(input.string) tmpfile.close input = StringIO.new(`#{sprintf(@conf.filter_command, tmpfile.path)}`) raise('cannot execute filter command.') if ($?.to_i != 0) print_debug("read_title_body: execute filter."); end title = convert_to_server(input.gets.chomp) body = convert_to_server(input.read) return([title, body]); rescue error_exit("read_title_body: #$!") end end # Convert from client to server. def convert_to_server(str) return '' unless str return str if @conf.server_encoding == @conf.client_encoding return Iconv.conv(@conf.server_encoding, @conf.client_encoding, str) end # Convert from server to client. def convert_to_client(str) return '' unless str return str if @conf.server_encoding == @conf.client_encoding return Iconv.conv(@conf.client_encoding, @conf.server_encoding, str) end # Get "YYYYMMDDhhmmss" for now. def get_timestamp return Time.now.localtime.strftime('%Y%m%d%H%M%S') end end begin routine = MainRoutine.new routine.run end