etvnet-seek 0.7.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +26 -0
- data/Gemfile +14 -0
- data/README +20 -0
- data/Rakefile +59 -0
- data/VERSION +1 -0
- data/bin/etvnet-seek +27 -0
- data/bin/etvnet-seek.bat +6 -0
- data/etvnet-seek.gemspec +98 -0
- data/lib/etvnet_seek/commander.rb +105 -0
- data/lib/etvnet_seek/cookie_helper.rb +107 -0
- data/lib/etvnet_seek/core/access_page.rb +19 -0
- data/lib/etvnet_seek/core/base_page.rb +43 -0
- data/lib/etvnet_seek/core/browse_media_item.rb +41 -0
- data/lib/etvnet_seek/core/catalog_item.rb +38 -0
- data/lib/etvnet_seek/core/catalog_page.rb +29 -0
- data/lib/etvnet_seek/core/channel_media_item.rb +17 -0
- data/lib/etvnet_seek/core/channels_page.rb +29 -0
- data/lib/etvnet_seek/core/group_media_item.rb +7 -0
- data/lib/etvnet_seek/core/group_page.rb +42 -0
- data/lib/etvnet_seek/core/home_page.rb +18 -0
- data/lib/etvnet_seek/core/login_page.rb +49 -0
- data/lib/etvnet_seek/core/media_info.rb +31 -0
- data/lib/etvnet_seek/core/media_item.rb +52 -0
- data/lib/etvnet_seek/core/media_page.rb +41 -0
- data/lib/etvnet_seek/core/new_item.rb +9 -0
- data/lib/etvnet_seek/core/new_items_page.rb +20 -0
- data/lib/etvnet_seek/core/page.rb +19 -0
- data/lib/etvnet_seek/core/page_factory.rb +51 -0
- data/lib/etvnet_seek/core/search_page.rb +42 -0
- data/lib/etvnet_seek/core/service_call.rb +32 -0
- data/lib/etvnet_seek/easy_auth.rb +52 -0
- data/lib/etvnet_seek/etvnet_seek.rb +10 -0
- data/lib/etvnet_seek/link_info.rb +37 -0
- data/lib/etvnet_seek/main.rb +180 -0
- data/lib/etvnet_seek/user_selection.rb +26 -0
- data/lib/media_converter.rb +85 -0
- data/lib/progressbar.rb +237 -0
- data/lib/runglish.rb +131 -0
- data/spec/etvnet_seek_spec.rb +28 -0
- metadata +200 -0
@@ -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
|