etvnet_seek 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +5 -1
- data/Rakefile +45 -0
- data/bin/etvnet_seek +8 -15
- data/etvnet_seek.gemspec +10 -5
- data/lib/etvnet_seek/commander.rb +93 -0
- data/lib/{cookie_helper.rb → etvnet_seek/cookie_helper.rb} +21 -32
- data/lib/etvnet_seek/core/access_page.rb +18 -0
- data/lib/etvnet_seek/core/announces_page.rb +34 -0
- data/lib/etvnet_seek/core/base_page.rb +129 -0
- data/lib/etvnet_seek/core/browse_media_item.rb +40 -0
- data/lib/etvnet_seek/core/channel_media_item.rb +13 -0
- data/lib/etvnet_seek/core/channels_page.rb +37 -0
- data/lib/etvnet_seek/core/freetv_page.rb +38 -0
- data/lib/etvnet_seek/core/group_media_item.rb +14 -0
- data/lib/etvnet_seek/core/group_page.rb +45 -0
- data/lib/etvnet_seek/core/login_page.rb +46 -0
- data/lib/etvnet_seek/core/main_page.rb +20 -0
- data/lib/etvnet_seek/core/media_info.rb +31 -0
- data/lib/etvnet_seek/core/media_item.rb +45 -0
- data/lib/etvnet_seek/core/media_page.rb +43 -0
- data/lib/etvnet_seek/core/page.rb +19 -0
- data/lib/etvnet_seek/core/page_factory.rb +52 -0
- data/lib/etvnet_seek/core/search_page.rb +7 -0
- data/lib/etvnet_seek/core/service_call.rb +32 -0
- data/lib/etvnet_seek/link_info.rb +33 -0
- data/lib/etvnet_seek/main.rb +177 -0
- data/lib/etvnet_seek/user_selection.rb +45 -0
- data/lib/runglish.rb +115 -117
- data/spec/etvnet_seek_spec.rb +7 -13
- metadata +76 -6
- data/lib/main.rb +0 -95
- data/lib/url_seeker.rb +0 -97
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
class LoginPage < ServiceCall
|
4
|
+
LOGIN_URL = "#{Page::BASE_URL}/cgi-bin/video/login.fcgi"
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
super(LOGIN_URL)
|
8
|
+
end
|
9
|
+
|
10
|
+
def login username, password
|
11
|
+
headers = { "Content-Type" => "application/x-www-form-urlencoded" }
|
12
|
+
|
13
|
+
response = post({ 'action' => 'login', 'username' => username, 'pwd'=> password }, headers)
|
14
|
+
|
15
|
+
cookie = response.response['set-cookie']
|
16
|
+
|
17
|
+
unless cookie.nil?
|
18
|
+
cookie = cleanup_cookie(cookie)
|
19
|
+
end
|
20
|
+
|
21
|
+
cookie
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def cleanup_cookie cookie
|
27
|
+
auth, expires = CookieHelper.get_auth_and_expires(cookie)
|
28
|
+
username = CookieHelper.get_username(cookie)
|
29
|
+
path = "/"
|
30
|
+
domain = ".etvnet.ca"
|
31
|
+
|
32
|
+
cookies_text = <<-TEXT
|
33
|
+
auth=#{auth}; expires=#{expires}; path=#{path}; domain=#{domain}
|
34
|
+
username=#{username}; expires=#{expires}; path=#{path}; domain=#{domain}
|
35
|
+
TEXT
|
36
|
+
|
37
|
+
new_cookie = ""
|
38
|
+
|
39
|
+
StringIO.new(cookies_text).each_line do |line|
|
40
|
+
new_cookie = new_cookie + line.strip + "; "
|
41
|
+
end
|
42
|
+
|
43
|
+
new_cookie
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class MainPage < BasePage
|
2
|
+
|
3
|
+
def items
|
4
|
+
list = []
|
5
|
+
|
6
|
+
document.css("#tblCategories a").each do |item|
|
7
|
+
text = item.css("img").at(0).attributes['alt'].value
|
8
|
+
href = item['href']
|
9
|
+
|
10
|
+
unless href =~ /forum.etvnet.ca/ or href =~ /action=browse_persons/ or href =~ /valentines2010/
|
11
|
+
list << MediaItem.new(text, href)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
list.delete_at(0)
|
16
|
+
|
17
|
+
list
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class MediaInfo
|
4
|
+
attr_reader :name, :link, :description, :description_production, :rtsp_link, :speech_lang,
|
5
|
+
:file_length, :channel, :datetime, :popularity, :duration, :rating
|
6
|
+
|
7
|
+
def initialize params
|
8
|
+
@link = params["REDIRECT_URL"]
|
9
|
+
@session_expired = (params["error_session_expire"] == 1)
|
10
|
+
@description_production = params["description_production"]
|
11
|
+
@rtsp_link = params["REDIRECT_URL_RTSP"]
|
12
|
+
@speech_lang = params["speech_lang"]
|
13
|
+
@name = params["name"]
|
14
|
+
@description = params["description"]
|
15
|
+
@file_length = params["file_length"]
|
16
|
+
@channel = params["channel"]
|
17
|
+
@datetime = params["datetime"]
|
18
|
+
@popularity = params["popularity_24h"]
|
19
|
+
@duration = params["duration"]
|
20
|
+
@rating = params["rating"]
|
21
|
+
end
|
22
|
+
|
23
|
+
def resolved?
|
24
|
+
not @link.nil? and not @link.strip.size == 0
|
25
|
+
end
|
26
|
+
|
27
|
+
def session_expired?
|
28
|
+
@session_expired
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class MediaItem
|
2
|
+
attr_reader :text, :link, :additional_info
|
3
|
+
attr_reader :underscore_name, :media_file
|
4
|
+
|
5
|
+
def initialize(text, link, additional_info = nil)
|
6
|
+
@text = text
|
7
|
+
@link = link
|
8
|
+
@additional_info = additional_info
|
9
|
+
|
10
|
+
@underscore_name = extract_underscore_name
|
11
|
+
@media_file = extract_media_file
|
12
|
+
end
|
13
|
+
|
14
|
+
def folder?
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
18
|
+
def ==(object)
|
19
|
+
object.text == text and object.link == link
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
text
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def extract_underscore_name
|
29
|
+
return nil if link.nil?
|
30
|
+
|
31
|
+
result = link.match(/(\w*)\/(\w*)\/(\w*)\/([\w|-]*)/)
|
32
|
+
|
33
|
+
return nil if result.nil?
|
34
|
+
|
35
|
+
(result.size > 3) ? result[4] : ""
|
36
|
+
end
|
37
|
+
|
38
|
+
def extract_media_file
|
39
|
+
return nil if link.nil?
|
40
|
+
|
41
|
+
result = link.match(/(\w*)\/(\w*)\/(\w*)\/([\w|-]*)/)
|
42
|
+
|
43
|
+
(not result.nil? and result.size > 2) ? result[3] : ""
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class MediaPage < BasePage
|
2
|
+
def items
|
3
|
+
list = []
|
4
|
+
|
5
|
+
document.css("b a.media_file").each do |item|
|
6
|
+
link = item.attributes['href'].value
|
7
|
+
new_link = list.select {|l| l.link == link}.empty?
|
8
|
+
|
9
|
+
if new_link
|
10
|
+
text = item.content.strip
|
11
|
+
additional_info = additional_info(item, 1)
|
12
|
+
|
13
|
+
text += additional_info unless additional_info.nil?
|
14
|
+
|
15
|
+
showtime = item.parent.parent.parent.css('td[1]').text.strip
|
16
|
+
year = item.parent.parent.next.next.content.strip
|
17
|
+
duration = ""
|
18
|
+
|
19
|
+
if link =~ /action=browse_container/
|
20
|
+
folder = true
|
21
|
+
link = link[Page::BASE_URL.size..link.size]
|
22
|
+
else
|
23
|
+
folder = false
|
24
|
+
duration = item.parent.parent.next.next.next.next.content.strip unless
|
25
|
+
item.parent.parent.next.next.next.next.nil?
|
26
|
+
end
|
27
|
+
|
28
|
+
record = BrowseMediaItem.new(text, link)
|
29
|
+
record.folder = folder
|
30
|
+
record.showtime = showtime
|
31
|
+
record.year = year
|
32
|
+
record.duration = duration
|
33
|
+
|
34
|
+
list << record
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
list
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
class Page < ServiceCall
|
4
|
+
BASE_URL = "http://www.etvnet.ca"
|
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,52 @@
|
|
1
|
+
|
2
|
+
require 'etvnet_seek/core/media_item'
|
3
|
+
require 'etvnet_seek/core/media_info'
|
4
|
+
require 'etvnet_seek/core/browse_media_item'
|
5
|
+
require 'etvnet_seek/core/channel_media_item'
|
6
|
+
require 'etvnet_seek/core/group_media_item'
|
7
|
+
|
8
|
+
require 'etvnet_seek/core/service_call'
|
9
|
+
require 'etvnet_seek/core/page'
|
10
|
+
require 'etvnet_seek/core/base_page'
|
11
|
+
require 'etvnet_seek/core/media_page'
|
12
|
+
require 'etvnet_seek/core/main_page'
|
13
|
+
require 'etvnet_seek/core/search_page'
|
14
|
+
require 'etvnet_seek/core/freetv_page'
|
15
|
+
require 'etvnet_seek/core/channels_page'
|
16
|
+
require 'etvnet_seek/core/group_page'
|
17
|
+
require 'etvnet_seek/core/announces_page'
|
18
|
+
require 'etvnet_seek/core/access_page'
|
19
|
+
require 'etvnet_seek/core/login_page'
|
20
|
+
|
21
|
+
class PageFactory
|
22
|
+
def self.create mode, params = []
|
23
|
+
url = (mode == 'search') ? nil : (params.class == String ? params : params[0])
|
24
|
+
|
25
|
+
case mode
|
26
|
+
when 'search' then
|
27
|
+
SearchPage.new *params
|
28
|
+
when 'main' then
|
29
|
+
MainPage.new
|
30
|
+
when 'channels' then
|
31
|
+
ChannelsPage.new
|
32
|
+
when 'best_ten' then
|
33
|
+
BestTenPage.new
|
34
|
+
when 'popular' then
|
35
|
+
PopularPage.new
|
36
|
+
when 'we_recommend' then
|
37
|
+
WeRecommendPage.new
|
38
|
+
when 'announces' then
|
39
|
+
AnnouncesPage.new
|
40
|
+
when 'freetv' then
|
41
|
+
FreetvPage.new
|
42
|
+
when 'media' then
|
43
|
+
MediaPage.new url
|
44
|
+
when 'access' then
|
45
|
+
AccessPage.new
|
46
|
+
when 'login' then
|
47
|
+
LoginPage.new
|
48
|
+
else
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
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,33 @@
|
|
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
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
require "highline/import"
|
2
|
+
require 'optparse'
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
require 'etvnet_seek/core/page_factory'
|
6
|
+
|
7
|
+
require 'etvnet_seek/cookie_helper'
|
8
|
+
require 'etvnet_seek/user_selection'
|
9
|
+
require 'etvnet_seek/link_info'
|
10
|
+
require 'etvnet_seek/commander'
|
11
|
+
require 'runglish'
|
12
|
+
|
13
|
+
class Main
|
14
|
+
COOKIE_FILE_NAME = ENV['HOME'] + "/.etvnet-seek"
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@cookie_helper = CookieHelper.new COOKIE_FILE_NAME
|
18
|
+
@commander = Commander.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def seek *params
|
22
|
+
if @commander.search_mode?
|
23
|
+
params = read_keywords(*params)
|
24
|
+
|
25
|
+
puts "Keywords: #{params}" if @commander.runglish_mode?
|
26
|
+
end
|
27
|
+
|
28
|
+
process @commander.get_initial_mode, params
|
29
|
+
end
|
30
|
+
|
31
|
+
def process mode, *params
|
32
|
+
page = PageFactory.create(mode, params)
|
33
|
+
|
34
|
+
if mode == 'access'
|
35
|
+
process_access page, params[0]
|
36
|
+
elsif mode == 'login'
|
37
|
+
item = params[0]
|
38
|
+
cookie = page.login(*get_credentials)
|
39
|
+
|
40
|
+
@cookie_helper.save_cookie cookie
|
41
|
+
|
42
|
+
process("access", item)
|
43
|
+
else
|
44
|
+
items = page.items
|
45
|
+
|
46
|
+
if items.size > 0
|
47
|
+
display_items items
|
48
|
+
display_bottom_menu_part(mode)
|
49
|
+
|
50
|
+
user_selection = read_user_selection items
|
51
|
+
|
52
|
+
if not user_selection.quit?
|
53
|
+
current_item = items[user_selection.index]
|
54
|
+
|
55
|
+
if mode == 'main'
|
56
|
+
case current_item.link
|
57
|
+
when /announces.html/ then
|
58
|
+
process('announces')
|
59
|
+
when /freeTV.html/ then
|
60
|
+
process('freetv')
|
61
|
+
when /category=/ then
|
62
|
+
process('media', current_item.link)
|
63
|
+
when /action=channels/ then
|
64
|
+
process('channels', current_item.link)
|
65
|
+
end
|
66
|
+
elsif mode == 'channels'
|
67
|
+
if user_selection.archive?
|
68
|
+
process('media', current_item.archive_link)
|
69
|
+
else
|
70
|
+
process('media', current_item.link)
|
71
|
+
end
|
72
|
+
else # media : announces, freetv, category
|
73
|
+
if current_item.folder? or current_item.link =~ /action=view_recommended/
|
74
|
+
process('media', current_item.link)
|
75
|
+
else
|
76
|
+
process("access", current_item)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def process_access page, item
|
85
|
+
cookie = @cookie_helper.load_cookie
|
86
|
+
|
87
|
+
if cookie.nil?
|
88
|
+
process("login", item)
|
89
|
+
else
|
90
|
+
result = CookieHelper.get_auth_and_expires(cookie)
|
91
|
+
cookie_expire_date = DateTime.strptime(result[1], "%A, %d-%b-%Y %H:%M:%S %Z")
|
92
|
+
|
93
|
+
if cookie_expire_date < DateTime.now # cookie expired?
|
94
|
+
@cookie_helper.delete_cookie
|
95
|
+
|
96
|
+
process("login", item)
|
97
|
+
else
|
98
|
+
media_info = page.request_media_info(item.media_file, cookie)
|
99
|
+
|
100
|
+
if media_info.session_expired?
|
101
|
+
@cookie_helper.delete_cookie
|
102
|
+
|
103
|
+
process("login", item)
|
104
|
+
else
|
105
|
+
LinkInfo.new(item, media_info)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def get_credentials
|
112
|
+
username = ask("Enter username : " )
|
113
|
+
password = ask("Enter password : " ) { |q| q.echo = '*' }
|
114
|
+
|
115
|
+
[username, password]
|
116
|
+
end
|
117
|
+
|
118
|
+
def display_items items
|
119
|
+
if items.size == 0
|
120
|
+
puts "Empty search result."
|
121
|
+
else
|
122
|
+
items.each_with_index do |item, index|
|
123
|
+
puts "#{index+1}. #{item}"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def display_bottom_menu_part mode
|
129
|
+
puts "<number> => today; <number>.a => archive" if mode == 'channels'
|
130
|
+
puts "q. to exit"
|
131
|
+
end
|
132
|
+
|
133
|
+
def launch_link link
|
134
|
+
if RUBY_PLATFORM =~ /(win|w)32$/
|
135
|
+
`start wmplayer #{link}`
|
136
|
+
elsif RUBY_PLATFORM =~ /darwin/
|
137
|
+
`open #{link}`
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
def read_keywords input
|
144
|
+
keywords = input
|
145
|
+
|
146
|
+
if(keywords.strip.size == 0)
|
147
|
+
while keywords.strip.size == 0
|
148
|
+
keywords = ask("Keywords: ")
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
if RUBY_PLATFORM =~ /mswin32/ or @commander.runglish_mode?
|
153
|
+
keywords = Runglish::LatToRusConverter.new.transliterate(keywords)
|
154
|
+
end
|
155
|
+
|
156
|
+
keywords
|
157
|
+
end
|
158
|
+
|
159
|
+
def read_user_selection items
|
160
|
+
user_selection = UserSelection.new
|
161
|
+
|
162
|
+
while true
|
163
|
+
user_selection.parse(ask("Select the number: "))
|
164
|
+
|
165
|
+
if not user_selection.blank?
|
166
|
+
if user_selection.quit? or user_selection.index < items.size
|
167
|
+
break
|
168
|
+
else
|
169
|
+
puts "Selection is out of range: [1..#{items.size}]"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
user_selection
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class UserSelection
|
2
|
+
attr_reader :index
|
3
|
+
|
4
|
+
def parse text
|
5
|
+
@blank = text.strip.size == 0
|
6
|
+
|
7
|
+
result = text.split
|
8
|
+
|
9
|
+
@index = result[0].to_i-1
|
10
|
+
@quit = (result & ['q', 'Q']).empty? ? false : true
|
11
|
+
@archive = (result & ['a', 'A']).empty? ? false : true
|
12
|
+
|
13
|
+
# @quit = (['q', 'Q'].include? text) ? true : false
|
14
|
+
# @blank = text.strip.size == 0
|
15
|
+
# dot_index = text.index('.')
|
16
|
+
#
|
17
|
+
# if not dot_index.nil?
|
18
|
+
# part1 = text[0..dot_index-1]
|
19
|
+
# part2 = text[dot_index+1..-1]
|
20
|
+
#
|
21
|
+
# @index = (part1.to_i)-1
|
22
|
+
#
|
23
|
+
# @archive = (['a', 'A'].include? part2) ? true : false
|
24
|
+
# else
|
25
|
+
# @index = text.to_i-1
|
26
|
+
# end
|
27
|
+
end
|
28
|
+
|
29
|
+
def quit?
|
30
|
+
@quit
|
31
|
+
end
|
32
|
+
|
33
|
+
def blank?
|
34
|
+
@blank
|
35
|
+
end
|
36
|
+
|
37
|
+
def archive?
|
38
|
+
@archive
|
39
|
+
end
|
40
|
+
|
41
|
+
def item items
|
42
|
+
items[index]
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|