etvnet-seek 0.7.2

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.
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