ubcbooker 0.1.0 → 0.2.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 615c2cd68c874a04a2ba4e9cbf6bab3cd0d5ee64
4
- data.tar.gz: 90f94a06606db213ff52446fb0b00d2b33ebcfbf
3
+ metadata.gz: feb2fd80cc7567a4cfd673c7245ac5bd4251fa8b
4
+ data.tar.gz: f1f96fc070d9e8f66d955f103fa3c3c950597a74
5
5
  SHA512:
6
- metadata.gz: e81ef07ceb6a01cd5f7dc437c4e4c3955d8304c4457bf9d7fb1487fdd204e7860534573c57654a6c56fac0b43230cca4718c7d7e96397677f42e5bc192b1f02a
7
- data.tar.gz: e5f4330ec570ff27dac25f3842ac236611c58e6650e602a4b320c335464a1c82cb31f3acf7ff7935c53650ce94c14bd01d7f933543111dcca605298777cc65df
6
+ metadata.gz: 702723bae1f951c48ceddbf121100b9858fc25e32aa48d00b9fd0cd26c5029cc34fdb2b6173e4bcee2077dc37114310fa4f3950cb6a35cd214822ce480107e6e
7
+ data.tar.gz: 9dd869bad331752435f49821fc93d1a345dcefbf99ff3a77f7a7ccafea3b25350f074404e7a0929eb4007e9aab49f3f59d22abc37270a5dd4a8b8580d7df0046
data/.gitignore CHANGED
@@ -1,6 +1,7 @@
1
1
  .DS_Store
2
2
  *.gem
3
3
  *.rbc
4
+ lib/ubcbooker/config.yml
4
5
  /.config
5
6
  /coverage/
6
7
  /InstalledFiles
@@ -25,6 +26,7 @@
25
26
  Gemfile.lock
26
27
  .ruby-version
27
28
  .ruby-gemset
29
+ .rspec_status
28
30
 
29
31
  # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
30
32
  .rvmrc
data/README.md CHANGED
@@ -4,7 +4,11 @@ CLI tool to book project rooms in UBC
4
4
 
5
5
  ## Installation
6
6
 
7
- Add this line to your application's Gemfile:
7
+ Install it as:
8
+
9
+ $ gem install ubcbooker
10
+
11
+ For use in your application, add this line to your Gemfile:
8
12
 
9
13
  ```ruby
10
14
  gem 'ubcbooker'
@@ -14,21 +18,20 @@ And then execute:
14
18
 
15
19
  $ bundle
16
20
 
17
- Or install it yourself as:
18
-
19
- $ gem install ubcbooker
20
-
21
21
  ## Usage
22
22
 
23
23
  ```
24
- # Call help
25
- $ ubcbooker -h
26
24
  # Book Computer Science project rooms
27
25
  $ ubcbooker cs
28
26
  # Book Sauder project rooms
29
27
  $ ubcbooker sauder
30
28
  ```
31
29
 
30
+ ```
31
+ # Call help
32
+ $ ubcbooker -h
33
+ ```
34
+
32
35
  ## Development
33
36
 
34
37
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. To install this gem onto your local machine, run `bundle exec rake install`.
data/bin/console CHANGED
@@ -2,13 +2,7 @@
2
2
 
3
3
  require "bundler/setup"
4
4
  require "ubcbooker"
5
+ require "pry"
5
6
 
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
7
+ Pry.start
8
8
 
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require "irb"
14
- IRB.start(__FILE__)
data/bin/ubcbooker ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "ubcbooker"
5
+
6
+ Ubcbooker::CLI.new.start
@@ -0,0 +1,31 @@
1
+ module Ubcbooker
2
+ class Account
3
+ attr_accessor :username, :password
4
+
5
+ def initialize
6
+ @config_path = File.expand_path("../config.yml", __FILE__)
7
+ @account_info = YAML.load_file(@config_path)
8
+ @username = @account_info["username"]
9
+ @password = @account_info["password"]
10
+ end
11
+
12
+ def write(username, password)
13
+ @account["username"] = username
14
+ @account["password"] = password
15
+ new_yml = YAML.dump(@account)
16
+ open(@config_path, "w") { |f| f.write(new_yml) }
17
+ @account = YAML.load_file(@config_path)
18
+ end
19
+
20
+ def print_supported_departments
21
+ puts "Supported department options in #{Ubcbooker::VERSION}:"
22
+ BOOKING_URL.keys.each do |d|
23
+ puts " - #{d}"
24
+ end
25
+ end
26
+
27
+ def defined?
28
+ return @account["username"] != "hoge" && @account["password"] != "hoge"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,156 @@
1
+ module Ubcbooker
2
+ class CLI
3
+ attr_accessor :options
4
+
5
+ def initialize
6
+ @config = Ubcbooker::Config.new
7
+ @options = nil
8
+ end
9
+
10
+ def ask_config
11
+ print "Your CWL username: "
12
+ username = gets.chomp
13
+ print "Your CWL password: "
14
+ # Hide the password input
15
+ password = STDIN.noecho(&:gets).chomp
16
+ @config.write(username, password)
17
+ puts
18
+ end
19
+
20
+ def parse_options
21
+
22
+ # This will hold the options we parse
23
+ options = {
24
+ name: nil,
25
+ date: nil,
26
+ time: nil,
27
+ department: nil,
28
+ }
29
+
30
+ # TODO: Change department to building
31
+ OptionParser.new do |parser|
32
+ parser.on("-b", "--building BUILDING", String, "Specify which department to book rooms from") do |v|
33
+ if CLI::Validator.is_valid_department(v)
34
+ options[:department] = v
35
+ else
36
+ raise Ubcbooker::Error::UnsupportedDepartment.new(v)
37
+ end
38
+ end
39
+
40
+ parser.on("-d", "--date DATE", String, "Specify date to book rooms for (MM/DD)") do |v|
41
+ if CLI::Validator.is_valid_date(v)
42
+ options[:date] = v
43
+ else
44
+ raise Ubcbooker::Error::UnsupportedDate.new(v)
45
+ end
46
+ end
47
+
48
+ parser.on("-h", "--help", "Show this help message") do
49
+ puts parser
50
+ puts
51
+ puts "ex. Book a room in CS from 11am to 1pm on March 5th with the name 'Study Group'"
52
+ puts " $>ubcbooker -b cs -n 'Study Group' -d 03/05 -t 11:00-13:00"
53
+ exit(0)
54
+ end
55
+
56
+ parser.on("-l", "--list", "List supported departments") do |v|
57
+ @config.print_supported_departments
58
+ exit(0)
59
+ end
60
+
61
+ parser.on("-n", "--name NAME", String, "Name of the booking") do |v|
62
+ if CLI::Validator.is_valid_name(v)
63
+ options[:name] = v
64
+ else
65
+ raise Ubcbooker::Error::ProfaneName.new(v)
66
+ end
67
+ end
68
+ parser.on("-t", "--time TIME", String,
69
+ "Specify time to book rooms for (HH:MM-HH:MM)") do |v|
70
+ if CLI::Validator.is_valid_time(v)
71
+ options[:time] = v
72
+ else
73
+ raise Ubcbooker::Error::UnsupportedTime.new(v)
74
+ end
75
+ end
76
+
77
+ parser.on("-u", "--update", "Update username and password") do |v|
78
+ ask_config
79
+ exit(0)
80
+ end
81
+
82
+ parser.on("-v", "--version", "Show version") do |v|
83
+ puts Ubcbooker::VERSION
84
+ exit(0)
85
+ end
86
+ end.parse!
87
+
88
+ spinner = get_spinner("Verifying inputs")
89
+ spinner.success("Done!") # Stop animation
90
+
91
+ if CLI::Validator.is_required_missing(options)
92
+ raise OptionParser::MissingArgument
93
+ end
94
+
95
+ return options
96
+ end
97
+
98
+ def get_options
99
+ option_errors = [
100
+ Ubcbooker::Error::UnsupportedDepartment,
101
+ Ubcbooker::Error::UnsupportedTime,
102
+ Ubcbooker::Error::UnsupportedDate,
103
+ Ubcbooker::Error::ProfaneName,
104
+ ]
105
+
106
+ begin
107
+ return parse_options
108
+ rescue OptionParser::MissingArgument
109
+ puts "Error: Missing Option\n".red <<
110
+ "One or more of required option are missing values\n" <<
111
+ "Please check if options -b, -d, -n, and -t all have values passed"
112
+ exit(1)
113
+ rescue *option_errors => e
114
+ puts e.message
115
+ exit(1)
116
+ end
117
+ end
118
+
119
+ def get_department_scraper(department)
120
+ case department
121
+ when "cs"
122
+ return Ubcbooker::Scraper::Cs
123
+ when "sauder_ugrad"
124
+ return Ubcbooker::Scraper::SauderUgrad
125
+ else
126
+ raise Ubcbooker::Error::UnsupportedDepartment.new(department)
127
+ end
128
+ end
129
+
130
+ def get_scraper(department, username, password)
131
+ scraper_client = get_department_scraper(department)
132
+ return scraper_client.new(username, password)
133
+ end
134
+
135
+ def get_spinner(text)
136
+ spinner = ::TTY::Spinner.new("[:spinner] #{text} ... ", format: :dots)
137
+ spinner.auto_spin # Automatic animation with default interval
138
+ return spinner
139
+ end
140
+
141
+ def start
142
+ @options = get_options
143
+ ask_config if !@config.defined?
144
+
145
+ @client = get_scraper(@options[:department],
146
+ @config.account["username"],
147
+ @config.account["password"])
148
+ begin
149
+ room_id = @client.book(@options)
150
+ puts "Success! #{room_id} is booked".green
151
+ rescue Ubcbooker::Error::NoAvailableRoom => e
152
+ puts e.message
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,50 @@
1
+ module Ubcbooker
2
+ class CLI
3
+ module Validator
4
+ def self.is_valid_department(d)
5
+ return BOOKING_URL.keys.include?(d.to_sym)
6
+ end
7
+
8
+ def self.is_valid_date(d)
9
+ date = nil
10
+ begin
11
+ date = Date.parse(d)
12
+ # Expect MM/DD
13
+ rescue ArgumentError
14
+ return false
15
+ end
16
+ return /^\d\d\/\d\d$/.match?(d) && # Match format
17
+ date.weekday? && # Not on weekend
18
+ !date.past? && # Not in the past
19
+ (date < Date.today + 7) # Within a week
20
+ end
21
+
22
+ def self.is_valid_time(t)
23
+ if /^\d\d:\d\d-\d\d:\d\d$/.match?(t)
24
+ times = t.split("-")
25
+ times.each do |time|
26
+ begin
27
+ DateTime.parse(time)
28
+ # Expect HH:MM
29
+ rescue ArgumentError
30
+ return false
31
+ end
32
+ end
33
+ return true
34
+ else
35
+ return false
36
+ end
37
+ end
38
+
39
+ def self.is_required_missing(options)
40
+ return options[:name].nil? || options[:date].nil? ||
41
+ options[:time].nil? || options[:department].nil?
42
+ end
43
+
44
+ # False if the name contains any profanity
45
+ def self.is_valid_name(name)
46
+ return !Obscenity.profane?(name)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,16 @@
1
+ class String
2
+ def black; "\e[30m#{self}\e[0m" end
3
+ def red; "\e[31m#{self}\e[0m" end
4
+ def green; "\e[32m#{self}\e[0m" end
5
+ def brown; "\e[33m#{self}\e[0m" end
6
+ def blue; "\e[34m#{self}\e[0m" end
7
+ def magenta; "\e[35m#{self}\e[0m" end
8
+ def cyan; "\e[36m#{self}\e[0m" end
9
+ def gray; "\e[37m#{self}\e[0m" end
10
+
11
+ def bold; "\e[1m#{self}\e[22m" end
12
+ def italic; "\e[3m#{self}\e[23m" end
13
+ def underline; "\e[4m#{self}\e[24m" end
14
+ def blink; "\e[5m#{self}\e[25m" end
15
+ def reverse_color; "\e[7m#{self}\e[27m" end
16
+ end
@@ -0,0 +1,39 @@
1
+ module Ubcbooker
2
+ class Config
3
+ attr_accessor :account
4
+
5
+ def initialize
6
+ @config_path = File.expand_path("../config.yml", __FILE__)
7
+ if !File.exist?(@config_path)
8
+ create_config_file(@config_path)
9
+ end
10
+
11
+ @account = YAML.load_file(@config_path)
12
+ end
13
+
14
+ def create_config_file(config_path)
15
+ File.open(config_path, "w") do |f|
16
+ f.write("---\nusername: sample\npassword: sample")
17
+ end
18
+ end
19
+
20
+ def write(username, password)
21
+ @account["username"] = username
22
+ @account["password"] = password
23
+ new_yml = YAML.dump(@account)
24
+ open(@config_path, "w") { |f| f.write(new_yml) }
25
+ @account = YAML.load_file(@config_path)
26
+ end
27
+
28
+ def print_supported_departments
29
+ puts "Supported department options in #{Ubcbooker::VERSION}:"
30
+ BOOKING_URL.keys.each do |d|
31
+ puts " - #{d}"
32
+ end
33
+ end
34
+
35
+ def defined?
36
+ return @account["username"] != "sample" && @account["password"] != "sample"
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,70 @@
1
+ module Ubcbooker
2
+ module Error
3
+ class UnsupportedDepartment < StandardError
4
+ attr_reader :message
5
+ def initialize(department = "unknown")
6
+ @message = "\"#{department}\" is an unsupported department\n".red <<
7
+ "Check supported departments with `ubcbooker -l`"
8
+ super
9
+ end
10
+ end
11
+
12
+ class UnsupportedDate < StandardError
13
+ attr_reader :message, :date
14
+ def initialize(date = "unknown")
15
+ @date = date
16
+ @message = "Error: Unsupported Date\n".red <<
17
+ "Date must not be:\n" <<
18
+ " - in the past\n" <<
19
+ " - in a weekend\n" <<
20
+ " - beyond a week\n" <<
21
+ "Please check if the time is in the format of MM/DD\n" <<
22
+ "Ex. 03/05"
23
+ super
24
+ end
25
+ end
26
+
27
+ class UnsupportedTime < StandardError
28
+ attr_reader :message, :time
29
+ def initialize(time = "unknown")
30
+ @time = time
31
+ @message = "Error: Unsupported Time\n".red <<
32
+ "Please check if the time is in the format of HH:MM-HH:MM\n" <<
33
+ "Ex. 11:00-13:00"
34
+ super
35
+ end
36
+ end
37
+
38
+ class ProfaneName < StandardError
39
+ attr_reader :message, :name
40
+ def initialize(name = "unknown")
41
+ @name = name
42
+ @message = "Error: Name includes profanity\n".red <<
43
+ "Remember that other students might see the booking name\n" <<
44
+ "Please try again with a different name"
45
+ super
46
+ end
47
+ end
48
+
49
+ class NoAvailableRoom < StandardError
50
+ attr_reader :message, :time_range
51
+ def initialize(time_range)
52
+ @time_range = time_range
53
+ @message = "Error: No Available Room\n".red <<
54
+ "There are no room available for #{time_range} range\n" <<
55
+ "Please try again with a different time range"
56
+ super
57
+ end
58
+ end
59
+
60
+ class LoginFailed < StandardError
61
+ attr_reader :message
62
+ def initialize
63
+ @message = "\nLogin Failed :/\n".red <<
64
+ "Please try logging in with a different username or password\n" <<
65
+ "You can use `-u` flag to update saved accout info"
66
+ super
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,3 @@
1
+ ---
2
+ username: sample
3
+ password: sample
@@ -0,0 +1,43 @@
1
+ module Ubcbooker
2
+ module Scraper
3
+ class BaseScraper
4
+ def initialize(username, password)
5
+ @agent = Mechanize.new do |agent|
6
+ agent.user_agent_alias = "Linux Mozilla"
7
+ end
8
+ @username = username
9
+ @password = password
10
+ end
11
+
12
+ def is_logged_in(page)
13
+ page_body = Nokogiri::HTML(page.body)
14
+ login_status_text = page_body.css("p").first.text
15
+ return !login_status_text.include?("Login Failed")
16
+ end
17
+
18
+ def populate_account_info(login_page)
19
+ username_feild = login_page.form.field_with(name: "j_username")
20
+ username_feild.value = @username
21
+ password_field = login_page.form.field_with(name: "j_password")
22
+ password_field.value = @password
23
+ redirect_page = login_page.form.submit
24
+ return redirect_page.form.submit
25
+ end
26
+
27
+ # Do login for UBC CWL system
28
+ def login_ubc_cwl(login_page)
29
+ begin
30
+ after_login_page = populate_account_info(login_page)
31
+ if is_logged_in(after_login_page)
32
+ return after_login_page
33
+ else
34
+ raise Ubcbooker::Error::LoginFailed
35
+ end
36
+ rescue Ubcbooker::Error::LoginFailed => e
37
+ puts e.message
38
+ exit(1)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,175 @@
1
+ module Ubcbooker
2
+ module Scraper
3
+ class Cs < BaseScraper
4
+ CS_ROOM_BASE_URL = "https://my.cs.ubc.ca/space/"
5
+ CS_BOOK_URL = "https://my.cs.ubc.ca/node/add/pr-booking"
6
+ CS_ROOMS = ["X139", "X141", "X151", "X153", # 1st Floor
7
+ "X237", "X239", "X241", # 2nd Floor
8
+ "X337", "X339", "X341"] # 3rd Floor
9
+
10
+ def book(options)
11
+ login
12
+ title = options[:name]
13
+ book_date = Date.parse(options[:date])
14
+ book_slot = get_time_slot(options[:time])
15
+
16
+ room_pages = batch_request(CS_ROOMS, book_date)
17
+ room_pages.select! { |r| is_available(r, book_date, book_slot) }
18
+
19
+ if room_pages.any?
20
+ # TODO: If -c CHOOSE option then run CLI-UI here
21
+ room = room_pages.first # Choose the first available room
22
+ submit_booking(room, title, book_date, book_slot)
23
+ return get_room_page_id(room)
24
+ else
25
+ raise Ubcbooker::Error::NoAvailableRoom.new(options[:time])
26
+ end
27
+ end
28
+
29
+ def login
30
+ spinner = get_spinner("Logging into CWL")
31
+ booking_url = BOOKING_URL[:cs]
32
+ @agent.get(booking_url) do |page|
33
+ login_page = page.link_with(text: "CWL Login Redirect").click
34
+ login_ubc_cwl(login_page)
35
+ end
36
+ spinner.success("Done!") # Stop animation
37
+ end
38
+
39
+ def submit_booking(room, title, book_date, book_slot)
40
+ spinner = get_spinner("Submitting booking request")
41
+ booking_form = @agent.get(CS_BOOK_URL).forms[1]
42
+ booking_form["title"] = title
43
+ book_date_str = book_date.strftime("%Y/%m/%d") # ex 2018/03/08
44
+ select_room_option(booking_form, room)
45
+ booking_form["field_date[und][0][value][date]"] = book_date_str
46
+ booking_form["field_date[und][0][value][time]"] = time_to_ampm(book_slot.min)
47
+ booking_form["field_date[und][0][value2][date]"] = book_date_str
48
+ booking_form["field_date[und][0][value2][time]"] = time_to_ampm(book_slot.max)
49
+ spinner.success("Done!")
50
+ booking_form.submit
51
+ end
52
+
53
+ # Select the form otpion with right room id
54
+ def select_room_option(booking_form, page)
55
+ room_id = get_room_page_id(page)
56
+ select_options = booking_form.field_with(name: "field_space_project_room[und]").options
57
+ select_options.each do |o|
58
+ if o.text.include?(room_id)
59
+ o.click
60
+ end
61
+ end
62
+ end
63
+
64
+ def get_room_page_id(page)
65
+ return page.form.action.split("/").last
66
+ end
67
+
68
+ def batch_request(room_list, book_date)
69
+ cookie = @agent.cookies.join("; ")
70
+ spinner = get_spinner("Checking room availabilities")
71
+
72
+ hydra = Typhoeus::Hydra.new
73
+ requests = room_list.map do |room_id|
74
+ room_url = get_room_cal_url(book_date, room_id)
75
+ request = Typhoeus::Request.new(room_url, headers: { Cookie: cookie })
76
+ hydra.queue(request)
77
+ request
78
+ end
79
+ hydra.run # Start requests
80
+ spinner.success("Done!")
81
+ return typhoeus_to_mechanize(requests)
82
+ end
83
+
84
+ def get_room_cal_url(book_date, room_id)
85
+ if CS_ROOMS.include?(room_id)
86
+ room_url = CS_ROOM_BASE_URL + "ICCS" + room_id
87
+ if is_next_month(book_date)
88
+ today = Date.today
89
+ month_query = "?month=" + today.year + "-" + (today.month + 1)
90
+ return room_url + month_query
91
+ else
92
+ return room_url
93
+ end
94
+ end
95
+ end
96
+
97
+ # Turn typhoneus obj to mechanize page obj
98
+ def typhoeus_to_mechanize(requests)
99
+ pages = requests.map do |request|
100
+ html = request.response.body
101
+ Mechanize::Page.new(nil, {"ontent-type" => "text/html"}, html, nil, @agent)
102
+ end
103
+ return pages
104
+ end
105
+
106
+ def is_next_month(date)
107
+ return date.month != Date.today.month
108
+ end
109
+
110
+ def is_available(room_page, book_date, book_slot)
111
+ slot_booked = get_slot_booked(room_page, book_date)
112
+ return !is_slot_booked(slot_booked, book_slot)
113
+ end
114
+
115
+ def is_slot_booked(slot_booked, book_slot)
116
+ booked = false
117
+ slot_booked.each do |s|
118
+ if s.include?(book_slot)
119
+ booked = true
120
+ end
121
+ end
122
+ return booked
123
+ end
124
+
125
+ def get_slot_booked(room_page, book_date)
126
+ day_div_id = get_date_div_id(book_date)
127
+ day_div = room_page.search("td##{day_div_id}").first
128
+ slot_num = day_div.search("div.item").size
129
+ start_times = day_div.search("span.date-display-start")
130
+ end_times = day_div.search("span.date-display-end")
131
+
132
+ slot_booked = []
133
+ (0..slot_num-1).each do |i|
134
+ slot_start = ampm_to_time(start_times[i].text) # 01:00
135
+ slot_end = ampm_to_time(end_times[i].text) # 05:00
136
+ slot_booked.push((slot_start..slot_end))
137
+ end
138
+ return slot_booked
139
+ end
140
+
141
+ def get_date_div_id(date)
142
+ date_div_base = "calendar_space_entity_project_room-"
143
+ date_div_base += "#{date.year}-#{date.strftime("%m")}-#{date.strftime("%d")}-0"
144
+ return date_div_base
145
+ end
146
+
147
+ def get_spinner(text)
148
+ spinner = ::TTY::Spinner.new("[:spinner] #{text} ... ", format: :dots)
149
+ spinner.auto_spin # Automatic animation with default interval
150
+ return spinner
151
+ end
152
+
153
+ # =========================
154
+ # Time operations
155
+
156
+ # Expect HH:MM-HH:MM
157
+ def get_time_slot(time_str)
158
+ times = time_str.split("-")
159
+ return (times[0]..times[1])
160
+ end
161
+
162
+ # 05:00pm -> 17:99
163
+ def ampm_to_time(str)
164
+ time = Time.parse(str)
165
+ return time.strftime("%H:%M")
166
+ end
167
+
168
+ # 17:00 -> 05:00pm
169
+ def time_to_ampm(str)
170
+ time = Time.parse(str)
171
+ return time.strftime("%I:%M%P")
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,2 @@
1
+ require_relative "base_scraper"
2
+ require_relative "cs"
@@ -1,3 +1,3 @@
1
1
  module Ubcbooker
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.1"
3
3
  end
data/lib/ubcbooker.rb CHANGED
@@ -1,50 +1,24 @@
1
- require "ubcbooker/version"
2
- require "io/console" # For handling password
3
- require "mechanize"
4
1
  require "pry"
2
+ require "business_time"
3
+ require "tty-spinner"
4
+ require "mechanize"
5
+ require "typhoeus"
6
+ require "obscenity"
7
+ require "io/console" # For handling password
8
+ require "optparse"
9
+ require "yaml"
5
10
 
6
11
  module Ubcbooker
7
- # TODO:
8
- # - Gemify
9
- # - TDD
10
- # - Catch when login fails
11
- # - Feature to save password
12
- # - Use Thor for CLI
13
- # - Divide up to files
14
-
15
- class User
16
- attr_accessor :username, :password
17
- end
18
-
19
- print "CWL username: "
20
- username = gets.chomp
21
- print "CWL password: "
22
- # Hide the password input
23
- password = STDIN.noecho(&:gets).chomp
24
- puts
25
-
26
- a = Mechanize.new do |agent|
27
- agent.user_agent_alias = "Linux Mozilla"
28
- end
29
-
30
- # What happens if I'm already logged in here?
31
- # Maybe check by seeing if the CWL link pops up after GET
32
-
33
- # Do login for UBC CWL system
34
- def self.login_ubc_cwl(login_page)
35
- username_feild = login_page.form.field_with(name => "j_username")
36
- username_feild.value = username
37
- password_field = login_page.form.field_with(name => "j_password")
38
- password_field.value = password
39
- redirect_page = login_page.form.submit
40
- return reditect_page.form.submit
41
- end
42
-
43
- puts "Requesting the page ..."
44
- a.get("https://my.cs.ubc.ca/docs/project-rooms-and-dlc") do |page|
45
- login_page = page.link_with(text => "CWL Login Redirect").click
46
- login_ubc_cwl(login_page)
47
- binding.pry
48
- end
49
-
12
+ BOOKING_URL = {
13
+ cs: "https://my.cs.ubc.ca/docs/project-rooms-and-dlc",
14
+ # sauder_ugrad: "https://booking.sauder.ubc.ca/ugr/cwl-login",
15
+ }
50
16
  end
17
+
18
+ require "ubcbooker/version"
19
+ require "ubcbooker/error"
20
+ require "ubcbooker/color"
21
+ require "ubcbooker/config"
22
+ require "ubcbooker/scrapers/scraper"
23
+ require "ubcbooker/cli_validator"
24
+ require "ubcbooker/cli"
data/ubcbooker.gemspec CHANGED
@@ -16,15 +16,18 @@ Gem::Specification.new do |spec|
16
16
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
17
  f.match(%r{^(test|spec|features)/})
18
18
  end
19
- spec.bindir = "exe"
20
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.executables = ["ubcbooker"]
21
20
  spec.require_paths = ["lib"]
22
21
 
23
22
  spec.add_dependency "mechanize", "~> 2.7"
23
+ spec.add_dependency "business_time", "~> 0.9.3"
24
+ spec.add_dependency "typhoeus", "~> 1.1"
25
+ spec.add_dependency "obscenity", "~> 1.0.2"
26
+ spec.add_dependency "tty-spinner", "~> 0.7.0"
24
27
 
25
- spec.add_development_dependency "bundler", "~> 1.16"
26
- spec.add_development_dependency "pry", "0.11.3"
27
28
  spec.add_development_dependency "rake", "~> 10.0"
28
29
  spec.add_development_dependency "rspec", "~> 3.0"
30
+ spec.add_development_dependency "pry", "~> 0.11.3"
31
+ spec.add_development_dependency "pry-byebug", "~> 3.4"
29
32
  spec.add_development_dependency "rubocop-github", "~> 0.8.1"
30
33
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ubcbooker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Atsushi Yamamoto
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2018-01-25 00:00:00.000000000 Z
11
+ date: 2018-03-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mechanize
@@ -25,33 +25,61 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.7'
27
27
  - !ruby/object:Gem::Dependency
28
- name: bundler
28
+ name: business_time
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.16'
34
- type: :development
33
+ version: 0.9.3
34
+ type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '1.16'
40
+ version: 0.9.3
41
41
  - !ruby/object:Gem::Dependency
42
- name: pry
42
+ name: typhoeus
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '='
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 0.11.3
48
- type: :development
47
+ version: '1.1'
48
+ type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '='
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 0.11.3
54
+ version: '1.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: obscenity
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 1.0.2
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 1.0.2
69
+ - !ruby/object:Gem::Dependency
70
+ name: tty-spinner
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.7.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.7.0
55
83
  - !ruby/object:Gem::Dependency
56
84
  name: rake
57
85
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +108,34 @@ dependencies:
80
108
  - - "~>"
81
109
  - !ruby/object:Gem::Version
82
110
  version: '3.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: pry
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.11.3
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.11.3
125
+ - !ruby/object:Gem::Dependency
126
+ name: pry-byebug
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '3.4'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '3.4'
83
139
  - !ruby/object:Gem::Dependency
84
140
  name: rubocop-github
85
141
  requirement: !ruby/object:Gem::Requirement
@@ -97,7 +153,8 @@ dependencies:
97
153
  description: CLI tool to book project rooms in UBC
98
154
  email:
99
155
  - hi@atsushi.me
100
- executables: []
156
+ executables:
157
+ - ubcbooker
101
158
  extensions: []
102
159
  extra_rdoc_files: []
103
160
  files:
@@ -107,13 +164,23 @@ files:
107
164
  - ".travis.yml"
108
165
  - CODE_OF_CONDUCT.md
109
166
  - Gemfile
110
- - Gemfile.lock
111
167
  - LICENSE
112
168
  - README.md
113
169
  - Rakefile
114
170
  - bin/console
115
171
  - bin/setup
172
+ - bin/ubcbooker
116
173
  - lib/ubcbooker.rb
174
+ - lib/ubcbooker/account.rb
175
+ - lib/ubcbooker/cli.rb
176
+ - lib/ubcbooker/cli_validator.rb
177
+ - lib/ubcbooker/color.rb
178
+ - lib/ubcbooker/config.rb
179
+ - lib/ubcbooker/error.rb
180
+ - lib/ubcbooker/sample.yml
181
+ - lib/ubcbooker/scrapers/base_scraper.rb
182
+ - lib/ubcbooker/scrapers/cs.rb
183
+ - lib/ubcbooker/scrapers/scraper.rb
117
184
  - lib/ubcbooker/version.rb
118
185
  - ubcbooker.gemspec
119
186
  homepage: https://github.com/jumbosushi/ubcbooker
data/Gemfile.lock DELETED
@@ -1,86 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- ubcbooker (0.1.0)
5
- mechanize (~> 2.7)
6
-
7
- GEM
8
- remote: https://rubygems.org/
9
- specs:
10
- ast (2.3.0)
11
- coderay (1.1.2)
12
- diff-lcs (1.3)
13
- domain_name (0.5.20170404)
14
- unf (>= 0.0.5, < 1.0.0)
15
- http-cookie (1.0.3)
16
- domain_name (~> 0.5)
17
- mechanize (2.7.5)
18
- domain_name (~> 0.5, >= 0.5.1)
19
- http-cookie (~> 1.0)
20
- mime-types (>= 1.17.2)
21
- net-http-digest_auth (~> 1.1, >= 1.1.1)
22
- net-http-persistent (~> 2.5, >= 2.5.2)
23
- nokogiri (~> 1.6)
24
- ntlm-http (~> 0.1, >= 0.1.1)
25
- webrobots (>= 0.0.9, < 0.2)
26
- method_source (0.9.0)
27
- mime-types (3.1)
28
- mime-types-data (~> 3.2015)
29
- mime-types-data (3.2016.0521)
30
- mini_portile2 (2.3.0)
31
- net-http-digest_auth (1.4.1)
32
- net-http-persistent (2.9.4)
33
- nokogiri (1.8.1)
34
- mini_portile2 (~> 2.3.0)
35
- ntlm-http (0.1.1)
36
- parallel (1.12.1)
37
- parser (2.4.0.2)
38
- ast (~> 2.3)
39
- powerpack (0.1.1)
40
- pry (0.11.3)
41
- coderay (~> 1.1.0)
42
- method_source (~> 0.9.0)
43
- rainbow (3.0.0)
44
- rake (10.5.0)
45
- rspec (3.6.0)
46
- rspec-core (~> 3.6.0)
47
- rspec-expectations (~> 3.6.0)
48
- rspec-mocks (~> 3.6.0)
49
- rspec-core (3.6.0)
50
- rspec-support (~> 3.6.0)
51
- rspec-expectations (3.6.0)
52
- diff-lcs (>= 1.2.0, < 2.0)
53
- rspec-support (~> 3.6.0)
54
- rspec-mocks (3.6.0)
55
- diff-lcs (>= 1.2.0, < 2.0)
56
- rspec-support (~> 3.6.0)
57
- rspec-support (3.6.0)
58
- rubocop (0.52.1)
59
- parallel (~> 1.10)
60
- parser (>= 2.4.0.2, < 3.0)
61
- powerpack (~> 0.1)
62
- rainbow (>= 2.2.2, < 4.0)
63
- ruby-progressbar (~> 1.7)
64
- unicode-display_width (~> 1.0, >= 1.0.1)
65
- rubocop-github (0.8.1)
66
- rubocop (~> 0.51)
67
- ruby-progressbar (1.9.0)
68
- unf (0.1.4)
69
- unf_ext
70
- unf_ext (0.0.7.4)
71
- unicode-display_width (1.3.0)
72
- webrobots (0.1.2)
73
-
74
- PLATFORMS
75
- ruby
76
-
77
- DEPENDENCIES
78
- bundler (~> 1.16)
79
- pry (= 0.11.3)
80
- rake (~> 10.0)
81
- rspec (~> 3.0)
82
- rubocop-github (~> 0.8.1)
83
- ubcbooker!
84
-
85
- BUNDLED WITH
86
- 1.16.1