etvnet-seek 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/CHANGES +26 -0
  2. data/Gemfile +14 -0
  3. data/README +20 -0
  4. data/Rakefile +59 -0
  5. data/VERSION +1 -0
  6. data/bin/etvnet-seek +27 -0
  7. data/bin/etvnet-seek.bat +6 -0
  8. data/etvnet-seek.gemspec +98 -0
  9. data/lib/etvnet_seek/commander.rb +105 -0
  10. data/lib/etvnet_seek/cookie_helper.rb +107 -0
  11. data/lib/etvnet_seek/core/access_page.rb +19 -0
  12. data/lib/etvnet_seek/core/base_page.rb +43 -0
  13. data/lib/etvnet_seek/core/browse_media_item.rb +41 -0
  14. data/lib/etvnet_seek/core/catalog_item.rb +38 -0
  15. data/lib/etvnet_seek/core/catalog_page.rb +29 -0
  16. data/lib/etvnet_seek/core/channel_media_item.rb +17 -0
  17. data/lib/etvnet_seek/core/channels_page.rb +29 -0
  18. data/lib/etvnet_seek/core/group_media_item.rb +7 -0
  19. data/lib/etvnet_seek/core/group_page.rb +42 -0
  20. data/lib/etvnet_seek/core/home_page.rb +18 -0
  21. data/lib/etvnet_seek/core/login_page.rb +49 -0
  22. data/lib/etvnet_seek/core/media_info.rb +31 -0
  23. data/lib/etvnet_seek/core/media_item.rb +52 -0
  24. data/lib/etvnet_seek/core/media_page.rb +41 -0
  25. data/lib/etvnet_seek/core/new_item.rb +9 -0
  26. data/lib/etvnet_seek/core/new_items_page.rb +20 -0
  27. data/lib/etvnet_seek/core/page.rb +19 -0
  28. data/lib/etvnet_seek/core/page_factory.rb +51 -0
  29. data/lib/etvnet_seek/core/search_page.rb +42 -0
  30. data/lib/etvnet_seek/core/service_call.rb +32 -0
  31. data/lib/etvnet_seek/easy_auth.rb +52 -0
  32. data/lib/etvnet_seek/etvnet_seek.rb +10 -0
  33. data/lib/etvnet_seek/link_info.rb +37 -0
  34. data/lib/etvnet_seek/main.rb +180 -0
  35. data/lib/etvnet_seek/user_selection.rb +26 -0
  36. data/lib/media_converter.rb +85 -0
  37. data/lib/progressbar.rb +237 -0
  38. data/lib/runglish.rb +131 -0
  39. data/spec/etvnet_seek_spec.rb +28 -0
  40. metadata +200 -0
@@ -0,0 +1,9 @@
1
+ class NewItem < MediaItem
2
+
3
+ attr_accessor :image
4
+
5
+ def to_s
6
+ "#{text} --- #{link}"
7
+ end
8
+
9
+ end
@@ -0,0 +1,20 @@
1
+ class NewItemsPage < BasePage
2
+
3
+ def items
4
+ list = []
5
+
6
+ document.css(".gallery ul li a").each do |item|
7
+ text = item.css("img").at(0).attributes['alt'].value.strip
8
+ src = item.css("img").at(0).attributes['src'].value.strip
9
+ href = item['href']
10
+
11
+ item = BrowseMediaItem.new(text, href)
12
+ item.image = src
13
+
14
+ list << item
15
+ end
16
+
17
+ list
18
+ end
19
+
20
+ end
@@ -0,0 +1,19 @@
1
+ require 'nokogiri'
2
+
3
+ class Page < ServiceCall
4
+ BASE_URL = "http://www.etvnet.com"
5
+
6
+ attr_reader :document
7
+
8
+ def initialize(url = BASE_URL)
9
+ super(url.index(BASE_URL).nil? ? "#{BASE_URL}/#{url}" : url)
10
+
11
+ @document = get_document
12
+ end
13
+
14
+ protected
15
+
16
+ def get_document
17
+ Nokogiri::HTML(get)
18
+ end
19
+ end
@@ -0,0 +1,51 @@
1
+ require 'etvnet_seek/core/media_item'
2
+ require 'etvnet_seek/core/new_item'
3
+ require 'etvnet_seek/core/catalog_item'
4
+ require 'etvnet_seek/core/media_info'
5
+ require 'etvnet_seek/core/browse_media_item'
6
+ require 'etvnet_seek/core/channel_media_item'
7
+ require 'etvnet_seek/core/group_media_item'
8
+
9
+ require 'etvnet_seek/core/service_call'
10
+ require 'etvnet_seek/core/page'
11
+ require 'etvnet_seek/core/home_page'
12
+ require 'etvnet_seek/core/base_page'
13
+ require 'etvnet_seek/core/media_page'
14
+ require 'etvnet_seek/core/search_page'
15
+ require 'etvnet_seek/core/channels_page'
16
+ require 'etvnet_seek/core/catalog_page'
17
+ require 'etvnet_seek/core/new_items_page'
18
+ require 'etvnet_seek/core/group_page'
19
+ require 'etvnet_seek/core/access_page'
20
+ require 'etvnet_seek/core/login_page'
21
+
22
+ class PageFactory
23
+ def self.create mode, params = []
24
+ url = (mode == 'search') ? nil : (params.class == String ? params : params[0])
25
+
26
+ case mode
27
+ when 'search' then
28
+ SearchPage.new *params
29
+ when 'main' then
30
+ HomePage.new
31
+ when 'channels' then
32
+ ChannelsPage.new
33
+ when 'catalog' then
34
+ CatalogPage.new
35
+ when 'best_hundred' then
36
+ BestHundredPage.new
37
+ when 'new_items' then
38
+ NewItemsPage.new
39
+ when 'premiere' then
40
+ PremierePage.new
41
+ when 'media' then
42
+ MediaPage.new url
43
+ when 'access' then
44
+ AccessPage.new
45
+ when 'login' then
46
+ LoginPage.new
47
+ else
48
+ nil
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,42 @@
1
+ require 'cgi'
2
+
3
+ class SearchPage < Page
4
+ SEARCH_URL = BASE_URL + "/search/"
5
+
6
+ def initialize(params)
7
+ super("#{SEARCH_URL}?q=#{CGI.escape(*params)}")
8
+ end
9
+
10
+ def items
11
+ list = []
12
+
13
+ document.css(".conteiner #results #table-onecolumn tr").each_with_index do |item, index|
14
+ next if index == 0
15
+
16
+ showtime = item.css("td[1]").text.strip
17
+ rating_image = item.css("td[2] img").at(0) ? item.css("td[2] img").at(0).attributes['src'].value.strip : ""
18
+ rating = item.css("td[3]") ? item.css("td[3]").text.strip : ""
19
+ name = item.css("td[4]").text.strip
20
+ duration = item.css("td[5]") ? item.css("td[5]").text.strip : ""
21
+ link = item.css("td[4] a").at(0)
22
+
23
+ href = link.attributes['href'].value
24
+
25
+ amount_expr = name.scan(%r{(.*)+\((\d*)+ (.*)+\)})[0]
26
+
27
+ folder = (not amount_expr.nil? and amount_expr.size > 1) ? true : false
28
+
29
+ record = BrowseMediaItem.new(name, href)
30
+ record.folder = folder
31
+ record.showtime = showtime
32
+ record.duration = duration
33
+ #record.channel = channel
34
+ record.rating_image = rating_image
35
+ record.rating = rating
36
+
37
+ list << record
38
+ end
39
+
40
+ list
41
+ end
42
+ end
@@ -0,0 +1,32 @@
1
+ require 'open-uri'
2
+
3
+ class ServiceCall
4
+ attr_reader :url
5
+
6
+ def initialize(url)
7
+ @url = url
8
+ end
9
+
10
+ def get
11
+ open(url)
12
+ end
13
+
14
+ def post params, headers
15
+ request = Net::HTTP::Post.new(url, headers)
16
+
17
+ request.set_form_data(params)
18
+
19
+ request(request)
20
+ end
21
+
22
+ protected
23
+
24
+ def request request
25
+ uri = URI.parse(url)
26
+
27
+ connection = Net::HTTP.new(uri.host, uri.port)
28
+
29
+ connection.request(request)
30
+ end
31
+
32
+ end
@@ -0,0 +1,52 @@
1
+ require 'digest/sha1'
2
+ require 'digest/sha2'
3
+
4
+ module EasyAuth
5
+ # http://techspeak.plainlystated.com/2010/03/drop-dead-simple-authentication-for.html
6
+
7
+ # To generate a hashed password (in irb):
8
+ # require 'easy_auth'
9
+ # EasyAuth.hash('my_password') # Put this in AUTHORIZED_USERS
10
+
11
+ AUTHORIZED_USERS = {
12
+ 'patrick' => "4ded8fa58a5c16298e665b35353555c89b786d8"
13
+ }
14
+
15
+ def self.hash_password(password)
16
+ #Digest::SHA256.digest(password)
17
+
18
+ Digest::SHA1.hexdigest(password)
19
+ end
20
+
21
+ def hash_password(password)
22
+ EasyAuth.hash_password(password)
23
+ end
24
+
25
+ def auth_digest(username, password)
26
+ key = "#{username}::#{hash_password(password)}::#{request.env['REMOTE_ADDR']}::#{request.env['HTTP_USER_AGENT']}"
27
+ hash_password key
28
+ end
29
+
30
+ def if_auth(username = request.cookies["username"], password = nil)
31
+ if password.nil?
32
+ key = request.cookies["key"]
33
+ else
34
+ key = auth_digest(username, hash_password(password))
35
+ end
36
+
37
+ if authorized?(username, key)
38
+ response.set_cookie("username", username)
39
+ response.set_cookie("key", key)
40
+ yield
41
+ else
42
+ @error = "Login failed"
43
+ erb :'/admin/login'
44
+ end
45
+ end
46
+
47
+ def authorized?(username, key)
48
+ return false unless AUTHORIZED_USERS.has_key?(username)
49
+
50
+ key == auth_digest(username, AUTHORIZED_USERS[username])
51
+ end
52
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems' unless RUBY_VERSION =~ /1.9.*/
2
+
3
+ $:.unshift(File.dirname(__FILE__)) unless
4
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
5
+
6
+ require 'etvnet_seek/core/page_factory'
7
+
8
+ require 'etvnet_seek/cookie_helper'
9
+ require 'etvnet_seek/link_info'
10
+
@@ -0,0 +1,37 @@
1
+ class LinkInfo
2
+ attr_reader :media_item, :media_info
3
+
4
+ def initialize(media_item, media_info)
5
+ @media_item = media_item
6
+ @media_info = media_info
7
+ end
8
+
9
+ def resolved?
10
+ not @media_info.link.nil? and not @media_info.link.strip.size == 0
11
+ end
12
+
13
+ def session_expired?
14
+ @media_info.session_expired
15
+ end
16
+
17
+ def text
18
+ media_item.text
19
+ end
20
+
21
+ def name
22
+ media_item.underscore_name
23
+ end
24
+
25
+ def media_file
26
+ media_item.media_file
27
+ end
28
+
29
+ def link
30
+ media_info.link
31
+ end
32
+
33
+ def rtsp_link
34
+ media_info.rtsp_link
35
+ end
36
+
37
+ end
@@ -0,0 +1,180 @@
1
+ require "highline/import"
2
+ require 'optparse'
3
+ require 'date'
4
+
5
+ require 'etvnet_seek/commander'
6
+ require 'etvnet_seek/user_selection'
7
+ require 'runglish'
8
+
9
+ class Main
10
+ COOKIE_FILE_NAME = ENV['HOME'] + "/.etvnet-seek"
11
+
12
+ def initialize
13
+ @cookie_helper = CookieHelper.new COOKIE_FILE_NAME
14
+ @commander = Commander.new
15
+ end
16
+
17
+ def seek *params
18
+ if @commander.search_mode?
19
+ params = read_keywords(*params)
20
+
21
+ puts "Keywords: #{params}" if @commander.runglish_mode?
22
+ end
23
+
24
+ process @commander.get_initial_mode, params
25
+ end
26
+
27
+ def process mode, *params
28
+ page = PageFactory.create(mode, params)
29
+
30
+ if mode == 'access'
31
+ process_access page, params[0]
32
+ elsif mode == 'login'
33
+ item = params[0]
34
+ cookie = page.login(*get_credentials)
35
+
36
+ @cookie_helper.save_cookie cookie
37
+
38
+ process("access", item)
39
+ else
40
+ items = page.items
41
+
42
+ if items.size > 0
43
+ display_items items
44
+ display_bottom_menu_part(mode)
45
+
46
+ user_selection = read_user_selection items
47
+
48
+ if not user_selection.quit?
49
+ current_item = items[user_selection.index]
50
+ if mode == 'main'
51
+ case current_item.link
52
+ when '/' then
53
+ process("main")
54
+ when /aired_today/ then
55
+ process('media', current_item.link)
56
+ when /catalog/ then
57
+ process('media', current_item.link)
58
+ when /tv_channels/ then
59
+ process('channels', current_item.link)
60
+ end
61
+ elsif mode == 'channels'
62
+ p user_selection.catalog?
63
+ if user_selection.catalog?
64
+ process('media', current_item.catalog_link)
65
+ else
66
+ process('media', current_item.link)
67
+ end
68
+ elsif mode == 'new_items'
69
+ process('access', current_item)
70
+ elsif mode == 'premiere'
71
+ process('access', current_item)
72
+ elsif mode == 'catalog'
73
+ p 'catalog'
74
+ process('media', current_item.link)
75
+ else # media
76
+ if current_item.folder? or current_item.link =~ /(catalog|tv_channel)/
77
+ process('media', current_item.link)
78
+ else
79
+ process("access", current_item)
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ def process_access page, item
88
+ cookie = @cookie_helper.load_cookie
89
+
90
+ if cookie.nil?
91
+ process("login", item)
92
+ else
93
+ expires = CookieHelper.get_expires(cookie)
94
+ cookie_expire_date = DateTime.strptime(expires, "%A, %d-%b-%Y %H:%M:%S %Z")
95
+
96
+ if cookie_expire_date < DateTime.now # cookie expired?
97
+ @cookie_helper.delete_cookie
98
+
99
+ process("login", item)
100
+ else
101
+ media_info = page.request_media_info(item.media_file, cookie)
102
+
103
+ if media_info.session_expired?
104
+ @cookie_helper.delete_cookie
105
+
106
+ process("login", item)
107
+ else
108
+ LinkInfo.new(item, media_info)
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ def get_credentials
115
+ username = ask("Enter username : " )
116
+ password = ask("Enter password : " ) { |q| q.echo = '*' }
117
+
118
+ [username, password]
119
+ end
120
+
121
+ def display_items items
122
+ if items.size == 0
123
+ puts "Empty search result."
124
+ else
125
+ items.each_with_index do |item, index|
126
+ puts "#{index+1}. #{item}"
127
+ end
128
+ end
129
+ end
130
+
131
+ def display_bottom_menu_part mode
132
+ puts "<number> => today; <number> c => catalog" if mode == 'channels'
133
+ puts "q. to exit"
134
+ end
135
+
136
+ def launch_link link
137
+ if RUBY_PLATFORM =~ /(win|w)32$/
138
+ `start wmplayer #{link}`
139
+ elsif RUBY_PLATFORM =~ /darwin/
140
+ `open '#{link}'`
141
+ end
142
+ end
143
+
144
+ private
145
+
146
+ def read_keywords input
147
+ keywords = input
148
+
149
+ if(keywords.strip.size == 0)
150
+ while keywords.strip.size == 0
151
+ keywords = ask("Keywords: ")
152
+ end
153
+ end
154
+
155
+ if RUBY_PLATFORM =~ /mswin32/ or @commander.runglish_mode?
156
+ keywords = Runglish::LatToRusConverter.new.transliterate(keywords)
157
+ end
158
+
159
+ keywords
160
+ end
161
+
162
+ def read_user_selection items
163
+ user_selection = UserSelection.new
164
+
165
+ while true
166
+ user_selection.parse(ask("Select the number: "))
167
+
168
+ if not user_selection.blank?
169
+ if user_selection.quit? or user_selection.index < items.size
170
+ break
171
+ else
172
+ puts "Selection is out of range: [1..#{items.size}]"
173
+ end
174
+ end
175
+ end
176
+
177
+ user_selection
178
+ end
179
+
180
+ end