votigoto 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2007-08-09
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/License.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 Jesse Newland
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest.txt ADDED
@@ -0,0 +1,20 @@
1
+ History.txt
2
+ License.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ bin/tivo_growl
7
+ lib/digest_auth.rb
8
+ lib/votigoto.rb
9
+ lib/votigoto/base.rb
10
+ lib/votigoto/show.rb
11
+ lib/votigoto/version.rb
12
+ scripts/txt2html
13
+ setup.rb
14
+ test/test_helper.rb
15
+ test/test_votigoto.rb
16
+ website/index.html
17
+ website/index.txt
18
+ website/javascripts/rounded_corners_lite.inc.js
19
+ website/stylesheets/screen.css
20
+ website/template.rhtml
data/README.txt ADDED
@@ -0,0 +1,9 @@
1
+ README for votigoto
2
+ ===================
3
+
4
+ >> require 'votigoto'
5
+ => true
6
+ >> tivo = Votigoto::Base.new("10.0.0.148","SEKRET_MEDIA_ACCESS_KEY")
7
+ => #<Votigoto::Base:0x14095d8 @mak="SEKRET_MEDIA_ACCESS_KEY", @ip="10.0.0.148">
8
+ >> tivo.shows.first.to_s
9
+ => "The Daily Show With Jon Stewart - Senator Joe Biden (D-Del.)."
data/Rakefile ADDED
@@ -0,0 +1,123 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/testtask'
5
+ require 'rake/packagetask'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/rdoctask'
8
+ require 'rake/contrib/rubyforgepublisher'
9
+ require 'fileutils'
10
+ require 'hoe'
11
+
12
+ include FileUtils
13
+ require File.join(File.dirname(__FILE__), 'lib', 'votigoto', 'version')
14
+
15
+ AUTHOR = 'Jesse Newland' # can also be an array of Authors
16
+ EMAIL = "jnewland@gmail.com"
17
+ DESCRIPTION = "Ruby abstraction of the TiVoToGo protocol. Access your list of recorded shows and programs on your Tivo."
18
+ GEM_NAME = 'votigoto' # what ppl will type to install your gem
19
+
20
+ @config_file = "~/.rubyforge/user-config.yml"
21
+ @config = nil
22
+ def rubyforge_username
23
+ unless @config
24
+ begin
25
+ @config = YAML.load(File.read(File.expand_path(@config_file)))
26
+ rescue
27
+ puts <<-EOS
28
+ ERROR: No rubyforge config file found: #{@config_file}"
29
+ Run 'rubyforge setup' to prepare your env for access to Rubyforge
30
+ - See http://newgem.rubyforge.org/rubyforge.html for more details
31
+ EOS
32
+ exit
33
+ end
34
+ end
35
+ @rubyforge_username ||= @config["username"]
36
+ end
37
+
38
+ RUBYFORGE_PROJECT = 'votigoto' # The unix name for your project
39
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
40
+ DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
41
+
42
+ NAME = "votigoto"
43
+ REV = nil
44
+ # UNCOMMENT IF REQUIRED:
45
+ # REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil
46
+ VERS = Votigoto::VERSION::STRING + (REV ? ".#{REV}" : "")
47
+ CLEAN.include ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store']
48
+ RDOC_OPTS = ['--quiet', '--title', 'votigoto documentation',
49
+ "--opname", "index.html",
50
+ "--line-numbers",
51
+ "--main", "README",
52
+ "--inline-source"]
53
+
54
+ class Hoe
55
+ def extra_deps
56
+ @extra_deps.reject { |x| Array(x).first == 'hoe' }
57
+ end
58
+ end
59
+
60
+ # Generate all the Rake tasks
61
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
62
+ hoe = Hoe.new(GEM_NAME, VERS) do |p|
63
+ p.author = AUTHOR
64
+ p.description = DESCRIPTION
65
+ p.email = EMAIL
66
+ p.summary = DESCRIPTION
67
+ p.url = HOMEPATH
68
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
69
+ p.test_globs = ["test/**/test_*.rb"]
70
+ p.clean_globs |= CLEAN #An array of file patterns to delete on clean.
71
+
72
+ # == Optional
73
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
74
+ p.extra_deps = [ ['hpricot', '>= 0.5.145']] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
75
+ #p.spec_extras = {} # A hash of extra values to set in the gemspec.
76
+ end
77
+
78
+ CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\n\n")
79
+ PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
80
+ hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
81
+
82
+ desc 'Generate website files'
83
+ task :website_generate do
84
+ Dir['website/**/*.txt'].each do |txt|
85
+ sh %{ ruby scripts/txt2html #{txt} > #{txt.gsub(/txt$/,'html')} }
86
+ end
87
+ end
88
+
89
+ desc 'Upload website files to rubyforge'
90
+ task :website_upload do
91
+ host = "#{rubyforge_username}@rubyforge.org"
92
+ remote_dir = "/var/www/gforge-projects/#{PATH}/"
93
+ local_dir = 'website'
94
+ sh %{rsync -aCv #{local_dir}/ #{host}:#{remote_dir}}
95
+ end
96
+
97
+ desc 'Generate and upload website files'
98
+ task :website => [:website_generate, :website_upload, :publish_docs]
99
+
100
+ desc 'Release the website and new gem version'
101
+ task :deploy => [:check_version, :website, :release] do
102
+ puts "Remember to create SVN tag:"
103
+ puts "svn copy svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/trunk " +
104
+ "svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/tags/REL-#{VERS} "
105
+ puts "Suggested comment:"
106
+ puts "Tagging release #{CHANGES}"
107
+ end
108
+
109
+ desc 'Runs tasks website_generate and install_gem as a local deployment of the gem'
110
+ task :local_deploy => [:website_generate, :install_gem]
111
+
112
+ task :check_version do
113
+ unless ENV['VERSION']
114
+ puts 'Must pass a VERSION=x.y.z release version'
115
+ exit
116
+ end
117
+ unless ENV['VERSION'] == VERS
118
+ puts "Please update your version.rb to match the release version, currently #{VERS}"
119
+ exit
120
+ end
121
+ end
122
+
123
+
data/bin/tivo_growl ADDED
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # modified from code by Alex Payne and Matt Biddulph and Damien Tanner
4
+ # requires growlnotify
5
+
6
+ require 'rubygems'
7
+ require 'votigoto'
8
+ require 'fileutils'
9
+ require 'daemons'
10
+
11
+ template = <<EOF
12
+ # .tivo
13
+ #
14
+ # Please fill in fields like this:
15
+ #
16
+ # ip: 10.0.0.148
17
+ # media_access_key: 79TGYHUUY87T9GYU
18
+ #
19
+ ip:
20
+ media_access_key:
21
+ EOF
22
+
23
+ interval = 15 # minutes between checks
24
+ growlnotifybin = "/usr/local/bin/growlnotify"
25
+ timefile = ENV['HOME'] + "/.votigoto-date"
26
+
27
+ # ensure config file for twitter gem exists
28
+ begin
29
+ config = YAML::load open(ENV['HOME'] + "/.tivo")
30
+ rescue
31
+ open(ENV["HOME"] + '/.tivo','w').write(template)
32
+ config = YAML::load open(ENV['HOME'] + "/.tivo")
33
+ end
34
+
35
+ # make sure there's actually account information in there
36
+ if config == false or config['ip'] == nil or config['media_access_key'] == nil
37
+ puts "Please edit ~/.tivo to include your TiVo's IP and Media Access Key\nTextmate users: mate ~/.tivo"
38
+ exit(0)
39
+ end
40
+
41
+ Daemons.daemonize
42
+
43
+ loop do
44
+ begin
45
+ last = Time.parse(open(timefile).read)
46
+ rescue
47
+ last = Time.now.to_s
48
+ end
49
+
50
+ begin
51
+ tivo = Votigoto::Base.new(config['ip'], config['media_access_key'])
52
+
53
+ new_shows = tivo.shows(true).select { |show| show.capture_date > Time.parse(last.to_s) }
54
+
55
+ new_shows.each do |show|
56
+ system "#{growlnotifybin} -n votigoto -t \"#{show.source_station.capitalize}\" -m \"#{show.title}\""
57
+ end
58
+ rescue
59
+ # system "#{growlnotifybin} -n votigoto -t \"Votigo Error\" -m \"#{$!.to_s.gsub("`","'")}\""
60
+ end
61
+
62
+ tf = open(timefile, "w")
63
+ tf.write(Time.now.to_s)
64
+ tf.close
65
+
66
+ sleep(interval * 60)
67
+ end
@@ -0,0 +1,55 @@
1
+ # Written by Eric Hodel <drbrain@segment7.net>
2
+ ##
3
+ # HTTP Digest Authentication
4
+
5
+ module DigestAuth
6
+ @@nonce_count = -1
7
+ @md5 = Digest::MD5.new
8
+ CNONCE = @md5.hexdigest("%x" % (Time.now.to_i + rand(65535)))
9
+
10
+ def self.gen_auth_header(uri, auth_header, is_IIS = false)
11
+ @@nonce_count += 1
12
+
13
+ user = CGI.unescape uri.user
14
+ password = CGI.unescape uri.password
15
+
16
+ auth_header =~ /^(\w+) (.*)/
17
+
18
+ params = {}
19
+ $2.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 }
20
+
21
+ a_1 = "#{user}:#{params['realm']}:#{password}"
22
+ a_2 = "GET:#{uri.path}"
23
+ request_digest = ''
24
+ request_digest << @md5.hexdigest(a_1)
25
+ request_digest << ':' << params['nonce']
26
+ request_digest << ':' << ('%08x' % @@nonce_count)
27
+ request_digest << ':' << CNONCE
28
+ request_digest << ':' << params['qop']
29
+ request_digest << ':' << @md5.hexdigest(a_2)
30
+
31
+ header = ''
32
+ header << "Digest username=\"#{user}\", "
33
+ header << "realm=\"#{params['realm']}\", "
34
+ if is_IIS then
35
+ header << "qop=\"#{params['qop']}\", "
36
+ else
37
+ header << "qop=#{params['qop']}, "
38
+ end
39
+ header << "uri=\"#{uri.path}\", "
40
+ header << "nonce=\"#{params['nonce']}\", "
41
+ header << "nc=#{'%08x' % @@nonce_count}, "
42
+ header << "cnonce=\"#{CNONCE}\", "
43
+ header << "response=\"#{@md5.hexdigest(request_digest)}\""
44
+
45
+ return header
46
+ end
47
+ end
48
+
49
+ # if __FILE__ == $0 then
50
+ # uri = URI.parse "http://user:password@www.example.com/"
51
+ # header = "Digest qop=\"auth\", realm=\"www.example.com\", nonce=\"4107baa081a592a6021660200000cd6c5686ff5f579324402b374d83e2c9\""
52
+ #
53
+ # puts DigestAuth.gen_auth_header uri, header
54
+ # end
55
+ #
@@ -0,0 +1,64 @@
1
+ class Votigoto::Base
2
+
3
+ def initialize(ip,mak)
4
+ @ip = ip
5
+ @mak = mak
6
+ end
7
+
8
+ attr_reader :ip, :mak, :doc
9
+
10
+ def last_changed_date(reload=false)
11
+ load(reload)
12
+ Time.at(@doc.at("/tivocontainer/details/lastchangedate").inner_text.to_i(16))
13
+ end
14
+
15
+ def shows(reload=false)
16
+ load(reload)
17
+ return @shows if @shows
18
+ @shows = []
19
+ @doc.search("tivocontainer/item").each do |show|
20
+ @shows << Votigoto::Show.new(show)
21
+ end
22
+ @shows
23
+ end
24
+ alias_method :to_a, :shows
25
+
26
+ def show(program_id,reload=false)
27
+ show = shows(reload).select { |show| show.program_id == program_id.to_s }
28
+ show.length == 1 ? show[0] : nil
29
+ end
30
+
31
+ private
32
+
33
+ def getxml(uri = "TiVoConnect?Command=QueryContainer&Container=%2FNowPlaying&Recurse=Yes")
34
+ begin
35
+ uri = URI.parse "https://tivo:#{@mak}@#{@ip}/#{uri}"
36
+ rescue URI::InvalidURIError
37
+ puts 'Invalid TiVo URI'
38
+ end
39
+ http = Net::HTTP.new(uri.host, uri.port)
40
+ http.use_ssl = true
41
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
42
+ xml = ''
43
+ begin
44
+ Timeout::timeout(Votigoto::TIMEOUT) do
45
+ http.start do |http|
46
+ response = http.head uri.request_uri
47
+ authorization = DigestAuth.gen_auth_header uri, response['www-authenticate']
48
+ response = http.get uri.request_uri, 'Authorization' => authorization
49
+ xml = response.body
50
+ end
51
+ end
52
+ end
53
+ Hpricot(xml)
54
+ end
55
+
56
+ def load(reload=false)
57
+ if reload
58
+ @doc = getxml()
59
+ @shows = nil
60
+ end
61
+ @doc ||= getxml()
62
+ end
63
+
64
+ end
@@ -0,0 +1,36 @@
1
+ class Votigoto::Show
2
+ PROPS = {
3
+ :string => %w(title episode_title description source_station program_id series_id),
4
+ :int => %w(source_size duration source_channel),
5
+ :custom => %w(capture_date content_url details_url in_progress)
6
+ }
7
+ def initialize(hpricot)
8
+ PROPS[:string].each do |property|
9
+ eval(%Q[
10
+ @#{property} = hpricot.at("details/#{property.gsub(/_/,'')}").inner_text rescue nil
11
+ ])
12
+ end
13
+ PROPS[:int].each do |property|
14
+ eval(%Q[
15
+ @#{property} = hpricot.at("details/#{property.gsub(/_/,'')}").inner_text.to_i rescue nil
16
+ ])
17
+ end
18
+ @capture_date = Time.at(hpricot.at("details/capturedate").inner_text.to_i(16)) rescue nil
19
+ @content_url = hpricot.at("links/content/url").inner_text rescue nil
20
+ @details_url = hpricot.at("links/tivovideodetails/url").inner_text rescue nil
21
+ @in_progress = hpricot.at("details/inprogress").inner_text == "Yes" rescue false
22
+ end
23
+
24
+ (PROPS[:string]+PROPS[:int]+PROPS[:custom]).each do |property|
25
+ class_eval "attr_reader :#{property}"
26
+ end
27
+
28
+ def to_s
29
+ if self.episode_title.nil?
30
+ return self.title
31
+ else
32
+ return self.title + " - " + self.episode_title
33
+ end
34
+ end
35
+
36
+ end
@@ -0,0 +1,9 @@
1
+ module Votigoto #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 2
5
+ TINY = 0
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
data/lib/votigoto.rb ADDED
@@ -0,0 +1,16 @@
1
+ module Votigoto
2
+ TIMEOUT = 30
3
+ end
4
+
5
+ require 'net/https'
6
+ require 'digest_auth'
7
+ require 'rubygems'
8
+ require 'hpricot'
9
+ require 'timeout'
10
+ require 'digest/md5'
11
+ require 'uri'
12
+ require 'cgi'
13
+ require 'digest_auth'
14
+ require 'votigoto/base'
15
+ require 'votigoto/version'
16
+ require 'votigoto/show'
data/scripts/txt2html ADDED
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'redcloth'
5
+ require 'syntax/convertors/html'
6
+ require 'erb'
7
+ require File.dirname(__FILE__) + '/../lib/votigoto/version.rb'
8
+
9
+ version = Votigoto::VERSION::STRING
10
+ download = 'http://rubyforge.org/projects/votigoto'
11
+
12
+ class Fixnum
13
+ def ordinal
14
+ # teens
15
+ return 'th' if (10..19).include?(self % 100)
16
+ # others
17
+ case self % 10
18
+ when 1: return 'st'
19
+ when 2: return 'nd'
20
+ when 3: return 'rd'
21
+ else return 'th'
22
+ end
23
+ end
24
+ end
25
+
26
+ class Time
27
+ def pretty
28
+ return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}"
29
+ end
30
+ end
31
+
32
+ def convert_syntax(syntax, source)
33
+ return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^<pre>|</pre>$!,'')
34
+ end
35
+
36
+ if ARGV.length >= 1
37
+ src, template = ARGV
38
+ template ||= File.dirname(__FILE__) + '/../website/template.rhtml'
39
+
40
+ else
41
+ puts("Usage: #{File.split($0).last} source.txt [template.rhtml] > output.html")
42
+ exit!
43
+ end
44
+
45
+ template = ERB.new(File.open(template).read)
46
+
47
+ title = nil
48
+ body = nil
49
+ File.open(src) do |fsrc|
50
+ title_text = fsrc.readline
51
+ body_text = fsrc.read
52
+ syntax_items = []
53
+ body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)</>!m){
54
+ ident = syntax_items.length
55
+ element, syntax, source = $1, $2, $3
56
+ syntax_items << "<#{element} class='syntax'>#{convert_syntax(syntax, source)}</#{element}>"
57
+ "syntax-temp-#{ident}"
58
+ }
59
+ title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip
60
+ body = RedCloth.new(body_text).to_html
61
+ body.gsub!(%r!(?:<pre><code>)?syntax-temp-(d+)(?:</code></pre>)?!){ syntax_items[$1.to_i] }
62
+ end
63
+ stat = File.stat(src)
64
+ created = stat.ctime
65
+ modified = stat.mtime
66
+
67
+ $stdout << template.result(binding)