#!/usr/bin/env ruby # $Id$ # $URL$ # # Copyright (c) 2006-2007 Ward Wouts # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # require 'net/http' require 'uri' require 'rexml/document' require 'date' require 'getoptlong' def usage puts < use configuration from (default $HOME/.getdistortedrc) -h, --help show this message -l, --list list podcasts -p, --podcast only fetch -v, --verbose be more verbose EOT exit end def cmdline options = Hash.new begin opts = GetoptLong.new( [ "-c", "--configfile", GetoptLong::REQUIRED_ARGUMENT ], [ "-h", "--help", GetoptLong::NO_ARGUMENT ], [ "-l", "--list", GetoptLong::NO_ARGUMENT ], [ "-p", "--podcast", GetoptLong::REQUIRED_ARGUMENT ], [ "-v", "--verbose", GetoptLong::NO_ARGUMENT ] ) opts.quiet=true opts.each do |opt, arg| options[opt] = arg end rescue print "#{$!}\n" usage end if options["-h"] usage end if options["-v"] @verbose = true end return options end def verbose(msg) if @verbose puts msg end end def readconfig(configfile=nil) configfile = configfile.nil? ? "#{ENV['HOME']}/.getdistortedrc" : configfile load configfile end def listpodcasts for podcast in @podcasts.keys.sort puts podcast end exit end def fetch(uri_str, limit = 10) # You should choose better exception. raise ArgumentError, 'HTTP redirect too deep' if limit == 0 verbose("Fetching #{uri_str}\n\n") host = URI.parse(uri_str).host path = URI.parse(uri_str).path query = URI.parse(uri_str).query proxy_host = nil proxy_port = nil proxy_user = nil proxy_pass = nil if ENV['HTTP_PROXY'] uri = URI.parse(ENV['HTTP_PROXY']) proxy_host = uri.host proxy_port = uri.port proxy_user, proxy_pass = uri.userinfo.split(/:/) if uri.userinfo end Net::HTTP::Proxy(proxy_host, proxy_port, proxy_user, proxy_pass).start(host) {|http| if query req = Net::HTTP::Get.new("#{path}?#{query}") else req = Net::HTTP::Get.new("#{path}") end req.basic_auth @user, @pass response = http.request(req) case response when Net::HTTPSuccess then response when Net::HTTPRedirection then verbose("Redirecting to #{response['location']}") fetch(response['location'], limit - 1) when Net::HTTPNotFound then puts "404 Not Found #{uri_str}"; response.error! when Net::HTTPUnauthorized then puts "401 Authorization Required #{uri_str}"; response.error! else response.error! end } end def deleteold(podcast) Dir.open(@podcasts[podcast]["savedir"]).each {|entry| if File.file?("#{@podcasts[podcast]["savedir"]}/#{entry}") && ! @filelist[entry] puts " deleting #{entry}" File.unlink("#{@podcasts[podcast]["savedir"]}/#{entry}") end } end def getenclosure(podcast, item) enclosure = nil item.each_element{|x| if x.name == "enclosure" enclosure = x end } pubdate = item.get_elements("pubDate")[0] date = nil if ! pubdate.nil? date = Date.parse(pubdate.text).strftime("%Y%m%d") end if ! enclosure.nil? && ! enclosure.attribute("url").nil? cast = enclosure.attribute("url").value.to_s.dup filename = cast.dup filename.sub!(/^.*\//, "") if @podcasts[podcast]["rename"] replacement = @podcasts[podcast]["rename"][1].dup if replacement.match(/%%DATE%%/) replacement.gsub!(/%%DATE%%/, date) end filename.gsub!(@podcasts[podcast]["rename"][0], replacement) end @filelist[filename] = true if ! File.exists?("#{@podcasts[podcast]["savedir"]}/#{filename}") puts " getting #{@podcasts[podcast]["savedir"]}/#{filename}" begin response = fetch(cast) File.open("#{@podcasts[podcast]["savedir"]}/#{filename}", "w"){|f| f.print(response.body) } if @podcasts[podcast]["linkdir"] File.symlink("#{@podcasts[podcast]["savedir"]}/#{filename}", "#{@podcasts[podcast]["linkdir"]}/#{filename}") end return "#{@podcasts[podcast]["savedir"]}/#{filename}" rescue Timeout::Error, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT, Net::HTTPFatalError puts " error #{$!} fetching, skipping" end else return "#{@podcasts[podcast]["savedir"]}/#{filename}" end end end def getcasts(podcast=nil) worklist = Array.new if podcast.nil? worklist = @podcasts.keys.sort else worklist.push podcast end for podcast in worklist files = Array.new puts podcast begin @user = @podcasts[podcast]["username"].nil? ? nil : @podcasts[podcast]["username"] @pass = @podcasts[podcast]["password"].nil? ? nil : @podcasts[podcast]["password"] res = fetch(@podcasts[podcast]["rss"]) @filelist = {} # body.gsub!(/#{13.chr}#{10.chr}/, "#{10.chr}") xmldoc = REXML::Document.new(res.body) xmldoc.elements.each("rss/channel") {|item| item.each_element{|x| if x.name == "item" verbose("Item found:\n#{x}\n\n") files.push getenclosure(podcast, x) end } } if @podcasts[podcast]["delete"] deleteold(podcast) end if @podcasts[podcast]["m3u"] File.open(@podcasts[podcast]["m3u"], "w"){|f| files.each{|file| f.puts file } } end rescue rescue Timeout::Error, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT, Net::HTTPFatalError puts " error #{$!} fetching, skipping" end end end @verbose = false options = cmdline readconfig options["-l"].nil? || listpodcasts if options["-p"].nil? getcasts else getcasts(options["-p"]) end