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