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.
@@ -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,7 @@
1
+ class SearchPage < MediaPage
2
+ SEARCH_URL = BASE_URL + "/cgi-bin/video/eitv_browse.fcgi?action=search"
3
+
4
+ def initialize(params)
5
+ super("#{SEARCH_URL}&keywords=#{CGI.escape(*params)}&order_direction=-")
6
+ end
7
+ 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