syoboi_calendar 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,60 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ syoboi_calendar (0.0.1)
5
+ mechanize (>= 2.3)
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ addressable (2.2.7)
11
+ crack (0.3.1)
12
+ diff-lcs (1.1.3)
13
+ domain_name (0.5.2)
14
+ unf (~> 0.0.3)
15
+ mechanize (2.3)
16
+ domain_name (~> 0.5, >= 0.5.1)
17
+ mime-types (~> 1.17, >= 1.17.2)
18
+ net-http-digest_auth (~> 1.1, >= 1.1.1)
19
+ net-http-persistent (~> 2.5, >= 2.5.2)
20
+ nokogiri (~> 1.4)
21
+ ntlm-http (~> 0.1, >= 0.1.1)
22
+ webrobots (~> 0.0, >= 0.0.9)
23
+ mime-types (1.18)
24
+ multi_json (1.2.0)
25
+ net-http-digest_auth (1.2)
26
+ net-http-persistent (2.6)
27
+ nokogiri (1.5.2)
28
+ ntlm-http (0.1.1)
29
+ rake (0.9.2.2)
30
+ rspec (2.9.0)
31
+ rspec-core (~> 2.9.0)
32
+ rspec-expectations (~> 2.9.0)
33
+ rspec-mocks (~> 2.9.0)
34
+ rspec-core (2.9.0)
35
+ rspec-expectations (2.9.1)
36
+ diff-lcs (~> 1.1.3)
37
+ rspec-mocks (2.9.0)
38
+ simplecov (0.6.1)
39
+ multi_json (~> 1.0)
40
+ simplecov-html (~> 0.5.3)
41
+ simplecov-html (0.5.3)
42
+ simplecov-vim (0.0.1)
43
+ unf (0.0.5)
44
+ unf_ext
45
+ unf_ext (0.0.4)
46
+ webmock (1.8.6)
47
+ addressable (>= 2.2.7)
48
+ crack (>= 0.1.7)
49
+ webrobots (0.0.13)
50
+
51
+ PLATFORMS
52
+ ruby
53
+
54
+ DEPENDENCIES
55
+ rake (>= 0.9.2)
56
+ rspec (>= 2.9.0)
57
+ simplecov (>= 0.6.1)
58
+ simplecov-vim (>= 0.0.1)
59
+ syoboi_calendar!
60
+ webmock (>= 1.8.6)
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Ryo NAKAMURA
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # SyoboiCalendar
2
+
3
+ Simple gem for [SyoboiCalendar](http://cal.syoboi.jp/) to search Japanese anime lineup
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'syoboi_calendar'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install syoboi_calendar
18
+
19
+ ## Usage
20
+
21
+ ```
22
+ require "syoboi_calender"
23
+
24
+ client = SyoboiCalendar::Client.new(
25
+ :user => "r7kamura",
26
+ :pass => "********"
27
+ )
28
+ programs = client.search(
29
+ :first => true,
30
+ :range => "2012/4/1-2012/4/30"
31
+ )
32
+
33
+ p programs[0].name #=> "さんかれあ #1 私が…ゾンビに…なったら"
34
+ p programs[0].start_time #=> 2012-04-06 02:50:00 +0900
35
+ p programs[0].channel_name #=> "TBS"
36
+ ```
37
+
38
+ ## Contributing
39
+
40
+ 1. Fork it
41
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
42
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
43
+ 4. Push to the branch (`git push origin my-new-feature`)
44
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require "bundler/gem_tasks"
4
+
5
+ begin
6
+ require "bundler"
7
+ rescue LoadError
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `gem install bundler` to install bundler"
10
+ exit e.status_code
11
+ end
12
+
13
+ begin
14
+ Bundler.setup(:default, :development)
15
+ rescue Bundler::BundlerError => e
16
+ $stderr.puts e.message
17
+ $stderr.puts "Run `bundle install` to install missing gems"
18
+ exit e.status_code
19
+ end
20
+
21
+ require "rspec/core"
22
+ require "rspec/core/rake_task"
23
+
24
+ RSpec::Core::RakeTask.new(:spec)
25
+
26
+ task :default => :spec
@@ -0,0 +1,29 @@
1
+ # encoding: UTF-8
2
+
3
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
4
+ require "syoboi_calendar"
5
+
6
+ require "slop"
7
+ args = Slop.parse :help => true do
8
+ banner "usage: \n ruby #{__FILE__}"
9
+ on :k, :keyword=, "Keyword for title (ex. ひだまりスケッチ)"
10
+ on :r, :range=, "Date range (ex. 2012/4/1-2012/4/30)"
11
+ end
12
+ unless args[:keyword]
13
+ puts args.to_s.gsub(/\n\n/, "\n")
14
+ exit
15
+ end
16
+
17
+ client = SyoboiCalendar::Client.new
18
+ titles = client.search(
19
+ :mode => :title,
20
+ :keyword => args[:keyword],
21
+ )
22
+
23
+ require "awesome_print"
24
+ titles.each do |title|
25
+ ap title.name
26
+ ap title.url
27
+ ap title.voice_actor_map
28
+ puts
29
+ end
@@ -0,0 +1,34 @@
1
+ # encoding: UTF-8
2
+
3
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
4
+ require "syoboi_calendar"
5
+
6
+ require "slop"
7
+ args = Slop.parse :help => true do
8
+ on :u, :user=, "Username on SyoboiCalendar"
9
+ on :p, :pass=, "Password on SyoboiCalendar"
10
+ on :r, :range=, "Date range (e.g. 2012/4/1-2012/4/30)"
11
+ end
12
+
13
+ client = SyoboiCalendar::Client.new(
14
+ :user => args[:user],
15
+ :pass => args[:pass]
16
+ )
17
+ programs = client.search(
18
+ :first => true,
19
+ :range => args[:range] || [ # today .. today + 1.month
20
+ Time.now + 0,
21
+ Time.now + 60 * 60 * 24 * 31
22
+ ].map { |i| Time.at(i).strftime("%Y/%m/%d") }.join("-")
23
+ )
24
+
25
+ programs.uniq!(&:name)
26
+ programs.sort! { |a, b| a.start_time.to_i <=> b.start_time.to_i}
27
+ programs.each { |program|
28
+ puts "|%s|%-9.9s|%s|%s|" % [
29
+ program.start_time.strftime("%Y-%m-%d %H:%M"),
30
+ program.channel_name.tr("A-Z", "A-Z").tr("a-z", "a-z").tr(" ", ""),
31
+ program.name.gsub(/( #\d+).*/, '\1'),
32
+ {true => "✔", false => " "}[program.saisoku?]
33
+ ]
34
+ }
@@ -0,0 +1,9 @@
1
+ # gems
2
+ require "mechanize"
3
+
4
+ # libraries
5
+ require "syoboi_calendar/version"
6
+ require "syoboi_calendar/agent"
7
+ require "syoboi_calendar/title"
8
+ require "syoboi_calendar/program"
9
+ require "syoboi_calendar/client"
@@ -0,0 +1,55 @@
1
+ require "uri"
2
+ require "json"
3
+
4
+ module SyoboiCalendar
5
+ class Agent
6
+ BASE_URL = "http://cal.syoboi.jp"
7
+ SEARCH_URL = BASE_URL + "/find"
8
+ JSON_URL = BASE_URL + "/json.php"
9
+ LOGIN_URL = BASE_URL + "/usr"
10
+
11
+ # login is option to search with user channel setting
12
+ def initialize(opts = {})
13
+ if opts[:user] && opts[:pass]
14
+ login(opts[:user], opts[:pass])
15
+ end
16
+ end
17
+
18
+ def login(user, pass)
19
+ page = get LOGIN_URL
20
+ form = page.forms[1]
21
+ form.usr = user
22
+ form.pass = pass
23
+ mechanize.submit(form)
24
+ @logined = true
25
+ end
26
+
27
+ def login?
28
+ !!@logined
29
+ end
30
+
31
+ def json(query)
32
+ page = get JSON_URL, query
33
+ JSON.parse(page.content)
34
+ end
35
+
36
+ def search(query)
37
+ get SEARCH_URL, query
38
+ end
39
+
40
+ private
41
+
42
+ def get(url, hash = {})
43
+ mechanize.get(url + querinize(hash))
44
+ end
45
+
46
+ # change hash into URL query string
47
+ def querinize(hash)
48
+ "?" + hash.map { |k, v| "#{k}=#{URI.encode(v.to_s)}" }.join("&")
49
+ end
50
+
51
+ def mechanize
52
+ @mechanize ||= Mechanize.new
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,87 @@
1
+ module SyoboiCalendar
2
+ class Client
3
+ # user and pass are optional to search with user's channel setting
4
+ def initialize(opts = {})
5
+ @agent = Agent.new(:user => opts[:user], :pass => opts[:pass])
6
+ end
7
+
8
+ def login?
9
+ @agent.login?
10
+ end
11
+
12
+ # search programs
13
+ def search(args)
14
+ query = create_search_query(args)
15
+ page = @agent.search(query)
16
+
17
+ args[:mode] == :title ?
18
+ extract_titles(page) :
19
+ extract_programs(page)
20
+ end
21
+
22
+ private
23
+
24
+ # return Array of #<SyoboiCalendar::Program> by #<Mechanize::Page>
25
+ def extract_programs(page, opts = {})
26
+ page.search(".tframe tr").map do |tr|
27
+ args = {}
28
+
29
+ tr.search("td:nth-child(2) a").each do |a|
30
+ if match = a.attributes["href"].value.match(%r|/tid/(\d+)/time#(\d+)$|)
31
+ args[:tid] = match[1]
32
+ args[:pid] = match[2]
33
+ args[:name] = a.text
34
+ end
35
+ end
36
+
37
+ tr.search("td:nth-child(3)").each do |td|
38
+ args[:channel_name] = td.text
39
+ end
40
+
41
+ if args.has_key?(:tid)
42
+ Program.new(args)
43
+ else
44
+ nil
45
+ end
46
+ end.compact
47
+ end
48
+
49
+ # return Array of #<SyoboiCalendar::Title> by #<Mechanize::Page>
50
+ def extract_titles(page, opts = {})
51
+ page.search(".tframe tr").map do |tr|
52
+ args = {}
53
+
54
+ tr.search("td:nth-child(1) a").each do |a|
55
+ if match = a.attributes["href"].value.match(%r|/tid/(\d+)$|)
56
+ args[:tid] = match[1]
57
+ args[:name] = a.text
58
+ end
59
+ end
60
+
61
+ if args.has_key?(:tid)
62
+ Title.new(args)
63
+ else
64
+ nil
65
+ end
66
+ end.compact
67
+ end
68
+
69
+ # create hash for search query
70
+ def create_search_query(opts)
71
+ {
72
+ :sd => { nil => 2, :program => 2, :title => 0 }[opts[:mode]],
73
+ :r => { nil => 0, :all => 0, :past => 1, :future => 2}[opts[:range]] || 3,
74
+ :rd => opts[:range],
75
+ :kw => opts[:keyword],
76
+ :ch => opts[:channel],
77
+ :st => opts[:subtitle],
78
+ :cm => opts[:comment],
79
+ :uuc => opts[:uuc] || 1, # use user's channel setting
80
+ :v => opts[:v] || 0, # list view
81
+ :pfn => opts[:first] && 2, # first episode
82
+ :pfl => opts[:final] && 4, # final episode
83
+ :pfs => opts[:special] && 1, # special program
84
+ }.select { |k, v| v }
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,86 @@
1
+ module SyoboiCalendar
2
+ class Program
3
+ attr_reader(
4
+ :pid,
5
+ :tid,
6
+ :name,
7
+ :channel_name
8
+ )
9
+
10
+ # (1) :method_name => "NameInSyoboiCalendarResponse"
11
+ # (2) :method_name => ["NameInSyoboiCalendarResponse", callback]
12
+ EXT_PARAM_MAP = {
13
+ :channel_epg_url => "ChEPGURL",
14
+ :channel_id => "ChID",
15
+ :comment => "Comment",
16
+ :config_flag => "ConfFlag",
17
+ :count => "Count",
18
+ :end_time => ["EdTime", proc { |val| Time.at(val.to_i) }],
19
+ :start_time => ["StTime", proc { |val| Time.at(val.to_i) }],
20
+ :subtitle => "SubTitle",
21
+ :subtitle2 => "SubTitle2"
22
+ }
23
+
24
+ # Example:
25
+ # program.count
26
+ # program.start_time
27
+ # ...
28
+ EXT_PARAM_MAP.keys.each do |key|
29
+ define_method(key) do
30
+ update_detail unless @blob.has_key?(key)
31
+ @blob[key]
32
+ end
33
+ end
34
+
35
+ # NOTICE: @blob is empty hash at first
36
+ # For example,
37
+ # If self.start_time is called,
38
+ # then update_detail is called automatically,
39
+ # and @blob is updated to fill up @blob[:start_time]
40
+ def initialize(args)
41
+ @pid = args[:pid]
42
+ @tid = args[:tid]
43
+ @name = args[:name]
44
+ @channel_name = args[:channel_name]
45
+ @blob = {}
46
+ end
47
+
48
+ # SAISOKU means "the most earliest broadcasting in the World"
49
+ def saisoku?
50
+ !!title.first_channel.match(channel_name)
51
+ end
52
+
53
+ # update params from detail data
54
+ def update_detail
55
+ hash = get_detail
56
+ EXT_PARAM_MAP.each do |k, v|
57
+ if v.kind_of?(Array)
58
+ @blob[k] = v[1].call(hash[v[0]])
59
+ else
60
+ @blob[k] = hash[v]
61
+ end
62
+ end
63
+ end
64
+
65
+ def title
66
+ @title ||= Title.new(:tid => @tid)
67
+ end
68
+
69
+ private
70
+
71
+ # request detail data
72
+ def get_detail
73
+ hash = self.class.agent.json(
74
+ :Req => "ProgramByPID",
75
+ :PID => @pid,
76
+ )
77
+ hash = hash[hash.keys.first]
78
+ hash = hash[hash.keys.first]
79
+ hash
80
+ end
81
+
82
+ def self.agent
83
+ @agent ||= Agent.new
84
+ end
85
+ end
86
+ end