dominosjp 0.1.0
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +47 -0
- data/bin/dominosjp +12 -0
- data/lib/dominosjp.rb +74 -0
- data/lib/order_address.rb +76 -0
- data/lib/order_coupon.rb +95 -0
- data/lib/order_information.rb +117 -0
- data/lib/order_payment.rb +186 -0
- data/lib/order_review.rb +100 -0
- data/lib/pizza.rb +113 -0
- data/lib/pizza_selector.rb +68 -0
- data/lib/preferences.rb +21 -0
- data/lib/request.rb +91 -0
- data/lib/version.rb +4 -0
- metadata +143 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1a8eff1dcad6edc6236af5a90239ecd2afd0cdc3
|
4
|
+
data.tar.gz: 3ca1d4888bb591523b1dbe3921e71ff6c3ec811f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8c63d14703893d31846c4d2691b8c73ea49b5cda9ca69fa937cb40beff10e99a42820d562c8d0f7c50d15a47ff2e76f7796b73a9741f557e3c861dd913596793
|
7
|
+
data.tar.gz: 53dae35002494984757b89e65dbfda652ed6dfb26e0f9132741ee6948560076b33d5e7e8c84c297b80ec557e8c391678274b3ba307b09604984ea41e95e9149b
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2017 Mahdi Bchetnia
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# DominosJP ππ΅π―π΅
|
2
|
+
πDomino's Pizza Japan CLI π
|
3
|
+
|
4
|
+

|
5
|
+
|
6
|
+
### Requirements
|
7
|
+
|
8
|
+
- A [Domino's Pizza Japan](https://www.dominos.jp/eng/) account that you've ordered with at least once in the past
|
9
|
+
|
10
|
+
### Install
|
11
|
+
|
12
|
+
```bash
|
13
|
+
$ gem install dominosjp
|
14
|
+
```
|
15
|
+
|
16
|
+
### Usage
|
17
|
+
|
18
|
+
```bash
|
19
|
+
$ dominosjp
|
20
|
+
```
|
21
|
+
and follow the instructions
|
22
|
+
|
23
|
+
### Features
|
24
|
+
|
25
|
+
- Order a pizza in less than a minute
|
26
|
+
- Save your preferences for even faster ordering
|
27
|
+
- Finds the best coupon for your order from your coupon box, by calculating the real value
|
28
|
+
|
29
|
+
### Preferences
|
30
|
+
|
31
|
+
Not really pizza-related, but by providing default values in a `.dominosjp.yml` file in your home directory you can skip most steps. See [.dominosjp.yml](blob/master/.dominosjp.yml) for examples.
|
32
|
+
|
33
|
+
Note: All keys are optional. It even allows for partial credit card info.
|
34
|
+
|
35
|
+
### Limitations/TODO
|
36
|
+
|
37
|
+
(Prefixed by a difficulty estimation from 1 to 5)
|
38
|
+
|
39
|
+
- (1) Allow for paying via cash (credit card-only now)
|
40
|
+
- (5) Allow for selecting pizza toppings
|
41
|
+
- (4) Allow for selecting pizza size/cut type/number of slices
|
42
|
+
- (2) Allow for selecting sides (not only pizzas), (5) maybe even the special menu
|
43
|
+
- (5) Extra: Pizza Tracking via the CLI, and hopefully automate the Mystery Deal
|
44
|
+
|
45
|
+
### Contact
|
46
|
+
|
47
|
+
[@inket](https://github.com/inket) / [@inket](https://twitter.com/inket) on Twitter / [mahdi.jp](https://mahdi.jp)
|
data/bin/dominosjp
ADDED
data/lib/dominosjp.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "colorize"
|
3
|
+
require "highline"
|
4
|
+
require "inquirer"
|
5
|
+
require "nokogiri"
|
6
|
+
|
7
|
+
require_relative "request"
|
8
|
+
require_relative "preferences"
|
9
|
+
require_relative "order_address"
|
10
|
+
require_relative "order_coupon"
|
11
|
+
require_relative "order_information"
|
12
|
+
require_relative "order_payment"
|
13
|
+
require_relative "order_review"
|
14
|
+
require_relative "pizza"
|
15
|
+
require_relative "pizza_selector"
|
16
|
+
|
17
|
+
class DominosJP
|
18
|
+
attr_accessor :order_address, :order_information
|
19
|
+
attr_accessor :order_review
|
20
|
+
attr_accessor :order_coupon, :order_payment
|
21
|
+
|
22
|
+
def initialize(login_details = {})
|
23
|
+
@email = login_details[:email] || Preferences.instance.email
|
24
|
+
@password = login_details[:password]
|
25
|
+
|
26
|
+
self.order_address = OrderAddress.new
|
27
|
+
self.order_information = OrderInformation.new
|
28
|
+
self.order_review = OrderReview.new
|
29
|
+
self.order_coupon = OrderCoupon.new
|
30
|
+
self.order_payment = OrderPayment.new
|
31
|
+
end
|
32
|
+
|
33
|
+
def login
|
34
|
+
@email ||= Ask.input("Email")
|
35
|
+
@password ||= HighLine.new.ask("Password: ") { |q| q.echo = "*" }
|
36
|
+
|
37
|
+
Request.post(
|
38
|
+
"https://order.dominos.jp/eng/login/login/",
|
39
|
+
{ "emailAccount" => @email, "webPwd" => @password },
|
40
|
+
expect: :redirect, failure: "Couldn't log in successfully"
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
def order
|
45
|
+
order_address.input
|
46
|
+
order_address.validate
|
47
|
+
|
48
|
+
order_information.input
|
49
|
+
order_information.validate
|
50
|
+
order_information.display
|
51
|
+
order_information.confirm
|
52
|
+
|
53
|
+
PizzaSelector.select_pizzas
|
54
|
+
# TODO: allow selecting sides
|
55
|
+
|
56
|
+
order_review.display
|
57
|
+
|
58
|
+
order_coupon.total_price_without_tax = order_review.total_price_without_tax
|
59
|
+
order_coupon.input
|
60
|
+
order_coupon.validate
|
61
|
+
|
62
|
+
order_review.display
|
63
|
+
|
64
|
+
order_payment.default_name = order_information.name
|
65
|
+
order_payment.input
|
66
|
+
order_payment.validate
|
67
|
+
order_payment.display
|
68
|
+
|
69
|
+
order_review.page = order_payment.page
|
70
|
+
order_review.display
|
71
|
+
|
72
|
+
order_payment.confirm
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class OrderAddress
|
3
|
+
attr_accessor :address
|
4
|
+
|
5
|
+
def input
|
6
|
+
response = Request.get("https://order.dominos.jp/eng/receipt/",
|
7
|
+
expect: :ok, failure: "Couldn't get order types page")
|
8
|
+
|
9
|
+
addresses = Addresses.from(response.body)
|
10
|
+
index = Ask.list "Choose an address", addresses.selection_list
|
11
|
+
self.address = addresses[index]
|
12
|
+
end
|
13
|
+
|
14
|
+
def validate
|
15
|
+
raise "Missing attributes" unless address
|
16
|
+
|
17
|
+
# Get the default parameters and add in the delivery address
|
18
|
+
params = default_params.merge("todokeSeq" => address.id)
|
19
|
+
|
20
|
+
Request.post("https://order.dominos.jp/eng/receipt/setReceipt", params,
|
21
|
+
expect: :redirect, to: "https://order.dominos.jp/eng/receipt/input/",
|
22
|
+
failure: "Couldn't set the delivery address")
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def default_params
|
28
|
+
{
|
29
|
+
# Receipt method: 1=delivery, 3=pickup
|
30
|
+
"receiptMethod" => "1",
|
31
|
+
# Rest is untouched
|
32
|
+
"tenpoC" => "",
|
33
|
+
"jushoC" => "",
|
34
|
+
"kokyakuJushoBanchi" => "",
|
35
|
+
"banchiCheckBox" => "",
|
36
|
+
"buildNm" => "",
|
37
|
+
"buildCheckBox" => "",
|
38
|
+
"todokeShortNm" => "",
|
39
|
+
"kigyoNm" => "",
|
40
|
+
"bushoNm" => "",
|
41
|
+
"naisen" => "",
|
42
|
+
"targetYmd" => nil,
|
43
|
+
"targetYmdhm" => nil,
|
44
|
+
"gpsPinpointF" => false
|
45
|
+
}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Addresses < Array
|
50
|
+
def self.from(source)
|
51
|
+
doc = Nokogiri::HTML(source)
|
52
|
+
|
53
|
+
Addresses.new(
|
54
|
+
doc.css(".l-section.m-addressSelect .addressSelect_content").map { |el| Address.new(el) }
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
def selection_list
|
59
|
+
map(&:list_item)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Address
|
64
|
+
attr_accessor :id, :label, :address, :estimation
|
65
|
+
|
66
|
+
def initialize(element)
|
67
|
+
self.label = element.css(".addressSelect_labelName").text.strip
|
68
|
+
self.address = element.css(".addressSelect_information_address").text.strip
|
69
|
+
self.estimation = element.css(".time_content_text").text.strip
|
70
|
+
self.id = element.css("input[name=todokeSeq]").first["value"].strip
|
71
|
+
end
|
72
|
+
|
73
|
+
def list_item
|
74
|
+
[label, address, estimation].join("\n ")
|
75
|
+
end
|
76
|
+
end
|
data/lib/order_coupon.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class OrderCoupon
|
3
|
+
attr_accessor :total_price_without_tax
|
4
|
+
attr_accessor :add_coupon
|
5
|
+
attr_accessor :coupon
|
6
|
+
|
7
|
+
def input
|
8
|
+
self.add_coupon = Ask.confirm "Add a coupon?"
|
9
|
+
return unless add_coupon
|
10
|
+
|
11
|
+
response = Request.get("https://order.dominos.jp/eng/coupon/use/",
|
12
|
+
expect: :ok, failure: "Couldn't get coupons list")
|
13
|
+
|
14
|
+
coupons = Coupons.from(response.body, total_price_without_tax)
|
15
|
+
selected_coupon_index = Ask.list "Choose a coupon", coupons.selection_list
|
16
|
+
self.coupon = coupons[selected_coupon_index]
|
17
|
+
end
|
18
|
+
|
19
|
+
def validate
|
20
|
+
return unless add_coupon
|
21
|
+
|
22
|
+
unless coupon.usable?
|
23
|
+
puts "This coupon cannot be used."
|
24
|
+
return
|
25
|
+
end
|
26
|
+
|
27
|
+
Request.post("https://order.dominos.jp/eng/webapi/sides/setUserCoupon/", coupon.params,
|
28
|
+
expect: :ok, failure: "Couldn't add coupon")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Coupons < Array
|
33
|
+
def self.from(source, total_price_without_tax)
|
34
|
+
doc = Nokogiri::HTML(source)
|
35
|
+
coupons = doc.css("li").map { |item| Coupon.new(item, total_price_without_tax) }
|
36
|
+
|
37
|
+
# Sort coupons by real value, expiration date while deranking those that cannot be used (error)
|
38
|
+
coupons = [coupons.reject(&:error), coupons.select(&:error)].map do |coups|
|
39
|
+
coups.group_by(&:real_value).sort.reverse.map do |_real_value, same_value_coupons|
|
40
|
+
same_value_coupons.sort_by(&:expiry)
|
41
|
+
end
|
42
|
+
end.flatten
|
43
|
+
|
44
|
+
Coupons.new(coupons)
|
45
|
+
end
|
46
|
+
|
47
|
+
def selection_list
|
48
|
+
map(&:list_item)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Coupon
|
53
|
+
attr_accessor :name, :expiry, :error, :couponcd, :couponseq, :expires_soon, :real_value
|
54
|
+
|
55
|
+
def initialize(item, total_price_without_tax)
|
56
|
+
name_element_text = item.css("h4").text
|
57
|
+
coupon_name = name_element_text.sub("\\", "Β₯").sub("Expires soon", "")
|
58
|
+
expires_soon = name_element_text.include?("Expires soon") ? "Expires soon" : ""
|
59
|
+
|
60
|
+
coupon_link = item.css(".jso-userCuponUse").first || {}
|
61
|
+
|
62
|
+
yen_value = coupon_name.scan(/Β₯(\d+)/).flatten.first.to_i
|
63
|
+
percent_value = coupon_name.scan(/(\d+)%/).flatten.first.to_i
|
64
|
+
|
65
|
+
if yen_value != 0
|
66
|
+
real_value = yen_value * 1.08 # 8% tax
|
67
|
+
elsif percent_value != 0
|
68
|
+
real_value = (total_price_without_tax / (100 / percent_value)) * 1.08 # 8% tax
|
69
|
+
end
|
70
|
+
|
71
|
+
error = item.css(".m-input__error").text
|
72
|
+
error = error && error.strip != "" ? "\n #{error}" : nil
|
73
|
+
|
74
|
+
self.name = coupon_name
|
75
|
+
self.expiry = item.css(".m-entryPeriod").text.scan(/\d{4}-\d{2}-\d{2}/).first
|
76
|
+
self.error = error
|
77
|
+
self.couponcd = coupon_link["couponcd"]
|
78
|
+
self.couponseq = coupon_link["couponseq"]
|
79
|
+
self.expires_soon = expires_soon
|
80
|
+
self.real_value = real_value.to_i
|
81
|
+
end
|
82
|
+
|
83
|
+
def usable?
|
84
|
+
couponcd && couponseq
|
85
|
+
end
|
86
|
+
|
87
|
+
def params
|
88
|
+
{ couponcd: couponcd, couponseq: couponseq }.compact
|
89
|
+
end
|
90
|
+
|
91
|
+
def list_item
|
92
|
+
"#{name.colorize(:blue)} (-Β₯#{real_value.to_s.colorize(:green)}) "\
|
93
|
+
"#{expires_soon.colorize(:yellow)} #{expiry} #{error.to_s.colorize(:red)}".strip
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class OrderInformation
|
3
|
+
attr_accessor :name, :phone_number
|
4
|
+
|
5
|
+
def input
|
6
|
+
response = Request.get("https://order.dominos.jp/eng/receipt/input/",
|
7
|
+
expect: :ok, failure: "Couldn't get information input page")
|
8
|
+
|
9
|
+
saved_name = Name.from(response.body)
|
10
|
+
phone_numbers = PhoneNumbers.from(response.body)
|
11
|
+
|
12
|
+
self.name = Preferences.instance.name || Ask.input("Name", default: saved_name)
|
13
|
+
|
14
|
+
self.phone_number = phone_numbers.find_number(Preferences.instance.phone_number)
|
15
|
+
unless phone_number
|
16
|
+
phone_number_index = Ask.list "Phone Number", phone_numbers.selection_list
|
17
|
+
self.phone_number = phone_numbers[phone_number_index]
|
18
|
+
end
|
19
|
+
|
20
|
+
@first_response = response
|
21
|
+
end
|
22
|
+
|
23
|
+
def validate
|
24
|
+
raise "Missing attributes" unless name && phone_number
|
25
|
+
|
26
|
+
# Get the default parameters and add in the client name and phone number
|
27
|
+
params = default_params.merge(
|
28
|
+
"kokyakuNm" => name,
|
29
|
+
"telSeq" => phone_number.value
|
30
|
+
)
|
31
|
+
|
32
|
+
@second_response = Request.post("https://order.dominos.jp/eng/receipt/confirm", params,
|
33
|
+
expect: :ok, failure: "Couldn't set your information") do |resp|
|
34
|
+
resp.body.include?("Order Type, Day&Time and Your Store")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def display
|
39
|
+
doc = Nokogiri::HTML(@second_response.body)
|
40
|
+
info = doc.css(".m-input__heading").map(&:text).
|
41
|
+
zip(doc.css(".section_content_table_td").map(&:text)).to_h
|
42
|
+
@page_params = doc.css("input[type=hidden]").map do |input|
|
43
|
+
[input["name"], input["value"]]
|
44
|
+
end.to_h
|
45
|
+
|
46
|
+
info.each { |title, value| puts "#{title.colorize(:blue)}: #{value}" }
|
47
|
+
end
|
48
|
+
|
49
|
+
def confirm
|
50
|
+
raise "Stopped by user" unless Ask.confirm "Continue?"
|
51
|
+
|
52
|
+
Request.post("https://order.dominos.jp/eng/receipt/complete", @page_params,
|
53
|
+
expect: :redirect, to: "https://order.dominos.jp/eng/menu/",
|
54
|
+
failure: "Couldn't validate your information")
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def default_params
|
60
|
+
kokyaku_input = Nokogiri::HTML(@first_response.body).css("input[name=kokyakuC]").first
|
61
|
+
raise "Couldn't find client information" unless kokyaku_input
|
62
|
+
|
63
|
+
{
|
64
|
+
"kokyakuC" => kokyaku_input["value"],
|
65
|
+
# Rest is untouched
|
66
|
+
"errorMessage" => "",
|
67
|
+
"receiptMethod" => "1", # Receipt method again...
|
68
|
+
"deleteTelSeq" => "",
|
69
|
+
"telNoRadio" => "0",
|
70
|
+
"telNo" => ""
|
71
|
+
}
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class Name
|
76
|
+
def self.from(source)
|
77
|
+
doc = Nokogiri::HTML(source)
|
78
|
+
input = doc.css("input[name=kokyakuNm]").first
|
79
|
+
raise "Couldn't get name field from information input page" unless input
|
80
|
+
|
81
|
+
input["value"]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class PhoneNumbers < Array
|
86
|
+
def self.from(source)
|
87
|
+
doc = Nokogiri::HTML(source)
|
88
|
+
numbers = doc.css("select[name=telSeq] > option").map { |option| PhoneNumber.new(option) }
|
89
|
+
|
90
|
+
if numbers.empty?
|
91
|
+
raise "Couldn't find any saved phone numbers in the information input page"
|
92
|
+
end
|
93
|
+
|
94
|
+
PhoneNumbers.new(numbers)
|
95
|
+
end
|
96
|
+
|
97
|
+
def find_number(number)
|
98
|
+
detect { |phone_number| phone_number.number == number }
|
99
|
+
end
|
100
|
+
|
101
|
+
def selection_list
|
102
|
+
map(&:list_item)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class PhoneNumber
|
107
|
+
attr_accessor :number, :value
|
108
|
+
|
109
|
+
def initialize(option)
|
110
|
+
self.number = option.text
|
111
|
+
self.value = option["value"]
|
112
|
+
end
|
113
|
+
|
114
|
+
def list_item
|
115
|
+
number
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "credit_card_validations"
|
3
|
+
require "credit_card_validations/string"
|
4
|
+
|
5
|
+
class OrderPayment
|
6
|
+
attr_accessor :default_name
|
7
|
+
attr_accessor :note
|
8
|
+
attr_accessor :last_review
|
9
|
+
attr_accessor :page
|
10
|
+
|
11
|
+
def input
|
12
|
+
Request.get("https://order.dominos.jp/eng/regi/",
|
13
|
+
expect: :ok, failure: "Couldn't get payment page")
|
14
|
+
|
15
|
+
puts
|
16
|
+
puts
|
17
|
+
puts "#{"Payment information".colorize(:blue)} (you will be able to review your order later)"
|
18
|
+
|
19
|
+
@credit_card = Preferences.instance.credit_card || CreditCard.new
|
20
|
+
@credit_card.input(default_name)
|
21
|
+
|
22
|
+
self.note = Preferences.instance.note ||
|
23
|
+
Ask.input("Any special requests? (not food preparation requests)")
|
24
|
+
end
|
25
|
+
|
26
|
+
def validate
|
27
|
+
params = default_params.merge("bikoText" => note).merge(@credit_card.params)
|
28
|
+
response = Request.post("https://order.dominos.jp/eng/regi/confirm", params,
|
29
|
+
expect: :ok, failure: "Couldn't submit payment information")
|
30
|
+
doc = Nokogiri::HTML(response.body)
|
31
|
+
|
32
|
+
token_input = doc.css("input[name='org.apache.struts.taglib.html.TOKEN']").first
|
33
|
+
raise "Couldn't get token for order validation" unless token_input && token_input["value"]
|
34
|
+
|
35
|
+
@insert_params = doc.css("input").map { |input| [input["name"], input["value"]] }.to_h
|
36
|
+
|
37
|
+
self.last_review = OrderLastReview.new(doc)
|
38
|
+
self.page = response.body
|
39
|
+
end
|
40
|
+
|
41
|
+
def display
|
42
|
+
puts last_review
|
43
|
+
end
|
44
|
+
|
45
|
+
def confirm
|
46
|
+
puts
|
47
|
+
|
48
|
+
unless Ask.confirm "Place order?"
|
49
|
+
puts "Stopped by user"
|
50
|
+
return
|
51
|
+
end
|
52
|
+
|
53
|
+
Request.post("https://order.dominos.jp/eng/regi/insert", @insert_params,
|
54
|
+
expect: :redirect, to: %r{\Ahttps://order\.dominos\.jp/eng/regi/complete/\?},
|
55
|
+
failure: "Order couldn't be placed for some reason :(")
|
56
|
+
|
57
|
+
puts
|
58
|
+
puts "Success!"
|
59
|
+
puts "Be sure to check the Domino's Pizza website in your browser "\
|
60
|
+
"to track your order status via the Pizza Tracker, and win a Mystery Deal coupon"
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def default_params
|
66
|
+
{
|
67
|
+
"inquiryRiyoDStr" => "undefined",
|
68
|
+
"inquiryCardComCd" => "undefined",
|
69
|
+
"inquiryCardBrand" => "undefined",
|
70
|
+
"inquiryCreditCardNoXXX" => "undefined",
|
71
|
+
"inquiryGoodThruMonth" => "undefined",
|
72
|
+
"inquiryGoodThruYear" => "undefined",
|
73
|
+
"creditCard" => "undefined",
|
74
|
+
"receiptK" => "0",
|
75
|
+
"exteriorPayment" => "4",
|
76
|
+
"reuseCreditDiv" => "1",
|
77
|
+
"rakutenPayment" => "1",
|
78
|
+
"isDisplayMailmagaArea" => "true",
|
79
|
+
"isProvisionalKokyakuModalView" => "false",
|
80
|
+
"isProvisionalKokyaku" => "false"
|
81
|
+
}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class OrderLastReview
|
86
|
+
attr_accessor :doc
|
87
|
+
|
88
|
+
def initialize(doc)
|
89
|
+
self.doc = doc
|
90
|
+
end
|
91
|
+
|
92
|
+
def to_s
|
93
|
+
sections = doc.css(".l-section").map do |section|
|
94
|
+
next unless section.css(".m-heading__caption").count.positive?
|
95
|
+
|
96
|
+
section_name = section.css(".m-heading__caption").text.strip.gsub(/\s+/, " ").colorize(:green)
|
97
|
+
rows = section.css("tr").map do |row|
|
98
|
+
th = row.css(".m-input__heading").first || row.css("th").first
|
99
|
+
th_text = th.text.strip.gsub(/\s+/, " ").colorize(:blue)
|
100
|
+
td_text = row.css(".section_content_table_td").text.
|
101
|
+
gsub(/ +/, " ").gsub(/\t+/, "").gsub(/(?:\r\n)+/, "\r\n").strip
|
102
|
+
|
103
|
+
"#{th_text}\n#{td_text}"
|
104
|
+
end
|
105
|
+
|
106
|
+
"\n#{section_name}\n#{rows.join("\n")}"
|
107
|
+
end
|
108
|
+
|
109
|
+
sections.join("\n")
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class CreditCard
|
114
|
+
attr_accessor :number, :cvv
|
115
|
+
attr_accessor :expiration_date
|
116
|
+
attr_accessor :name_on_card
|
117
|
+
|
118
|
+
VALUES = {
|
119
|
+
visa: "00200",
|
120
|
+
mastercard: "00300",
|
121
|
+
jcb: "00500",
|
122
|
+
amex: "00400",
|
123
|
+
diners: "00100",
|
124
|
+
nicos: "00600"
|
125
|
+
}.freeze
|
126
|
+
|
127
|
+
def initialize(config = {})
|
128
|
+
info = config.map { |key, value| [(key.to_sym rescue key), value.to_s] }.to_h
|
129
|
+
|
130
|
+
self.number = info[:number] || ""
|
131
|
+
self.cvv = info[:cvv]
|
132
|
+
self.expiration_date = info[:expiration_date]
|
133
|
+
self.name_on_card = info[:name]
|
134
|
+
end
|
135
|
+
|
136
|
+
def input(default_name = nil)
|
137
|
+
loop do
|
138
|
+
until number.valid_credit_card_brand?(:visa, :mastercard, :jcb, :amex, :diners)
|
139
|
+
puts "Invalid card number" unless number == ""
|
140
|
+
self.number = Ask.input "Credit Card Number"
|
141
|
+
end
|
142
|
+
|
143
|
+
unless number.credit_card_brand == :diners
|
144
|
+
self.cvv ||= HighLine.new.ask("CVV: ") { |q| q.echo = "*" }
|
145
|
+
end
|
146
|
+
|
147
|
+
expiration_month, expiration_year = (expiration_date || "").split("/")
|
148
|
+
while !(1..12).cover?(expiration_month.to_i) || !(17..31).cover?(expiration_year.to_i)
|
149
|
+
self.expiration_date = Ask.input "Expiration Date (mm/yy)"
|
150
|
+
expiration_month, expiration_year = expiration_date.split("/")
|
151
|
+
end
|
152
|
+
|
153
|
+
self.name_on_card ||= Ask.input "Name on Card", default: default_name
|
154
|
+
|
155
|
+
break if valid?
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def params
|
160
|
+
{
|
161
|
+
"existCreditCardF" => "",
|
162
|
+
"reuseCredit_check" => "1", # Seems to be 1 but it doesn't save the CC info
|
163
|
+
"cardComCd" => VALUES[number.credit_card_brand],
|
164
|
+
"creditCardNo" => number,
|
165
|
+
"creditCardSecurityCode" => cvv,
|
166
|
+
"creditCardSignature" => name_on_card,
|
167
|
+
"goodThruMonth" => expiration_date.split("/").first.rjust(2, "0"),
|
168
|
+
"goodThruYear" => expiration_date.split("/").last
|
169
|
+
}
|
170
|
+
end
|
171
|
+
|
172
|
+
def valid?
|
173
|
+
response = Request.post(
|
174
|
+
"https://order.dominos.jp/eng/webapi/regi/validate/creditCard/", params,
|
175
|
+
expect: :ok, failure: "Couldn't validate credit card info"
|
176
|
+
)
|
177
|
+
|
178
|
+
result = JSON.parse(response.body)
|
179
|
+
if result["errorDetails"]
|
180
|
+
puts result
|
181
|
+
return false
|
182
|
+
end
|
183
|
+
|
184
|
+
true
|
185
|
+
end
|
186
|
+
end
|
data/lib/order_review.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class OrderReview
|
3
|
+
attr_accessor :page
|
4
|
+
attr_accessor :total_price, :total_price_without_tax
|
5
|
+
|
6
|
+
def display
|
7
|
+
puts
|
8
|
+
puts
|
9
|
+
puts "Review Your Order".colorize(:red)
|
10
|
+
|
11
|
+
source = page || default_page
|
12
|
+
|
13
|
+
# Order items
|
14
|
+
puts OrderItems.from(source)
|
15
|
+
puts CouponItems.from(source)
|
16
|
+
|
17
|
+
# General information
|
18
|
+
puts retrieve_prices(source)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def retrieve_prices(source)
|
24
|
+
doc = Nokogiri::HTML(source)
|
25
|
+
total_price_element = doc.css(".totalPrice_taxin")
|
26
|
+
total_price_title = total_price_element.css("dt").text.strip.gsub(/\s+/, " ")
|
27
|
+
total_price_string = total_price_element.css("dd").text.strip.gsub(/\s+/, " ")
|
28
|
+
|
29
|
+
self.total_price = total_price_string.delete(",").scan(/Β₯(\d+)/).flatten.first.to_i
|
30
|
+
self.total_price_without_tax = total_price / 108 * 100 # 8% tax
|
31
|
+
|
32
|
+
"\n#{total_price_title}: #{total_price_string.colorize(:red)}\n"\
|
33
|
+
"#{doc.css(".totalPrice_tax").text}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def default_page
|
37
|
+
Request.get("https://order.dominos.jp/eng/pizza/search/",
|
38
|
+
expect: :ok, failure: "Couldn't get pizza list page").body
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class OrderItems < Array
|
43
|
+
def self.from(source)
|
44
|
+
doc = Nokogiri::HTML(source)
|
45
|
+
order_items = doc.css(".m-side_orderItems li").map { |item| OrderItem.new(item) }
|
46
|
+
OrderItems.new(order_items)
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_s
|
50
|
+
map(&:to_s).join("\n")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class OrderItem
|
55
|
+
attr_accessor :name, :details
|
56
|
+
|
57
|
+
def initialize(item)
|
58
|
+
self.name = item.css(".orderItems_item_name").first.text.strip.gsub(/\s+/, " ")
|
59
|
+
# TODO: Get toppings list
|
60
|
+
|
61
|
+
details_element = item.css(".orderItems_item_detail")
|
62
|
+
details_dt = details_element.css("dt").map { |t| t.text.strip.gsub(/\s+/, " ") }
|
63
|
+
details_dd = details_element.css("dd").map { |t| t.text.strip.gsub(/\s+/, " ") }
|
64
|
+
|
65
|
+
self.details = details_dt.zip(details_dd).to_h
|
66
|
+
details["Price"] = item.css(".orderItems_item_price").text.strip.gsub(/\s+/, " ")
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_s
|
70
|
+
deets = details.map { |key, value| " #{key}: #{value}" }.join("\n")
|
71
|
+
"#{name.colorize(:blue)}\n#{deets}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class CouponItems < Array
|
76
|
+
def self.from(source)
|
77
|
+
doc = Nokogiri::HTML(source)
|
78
|
+
coupon_items = doc.css(".m-side_useCoupon li").map { |item| CouponItem.new(item) }
|
79
|
+
|
80
|
+
CouponItems.new(coupon_items)
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_s
|
84
|
+
return unless count.positive?
|
85
|
+
"\nCoupons".colorize(:green)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class CouponItem
|
90
|
+
attr_accessor :name, :value
|
91
|
+
|
92
|
+
def initialize(item)
|
93
|
+
self.name = item.css(".useCoupon_coupons_name").text.strip.gsub(/\s+/, " ").sub("\\", "Β₯")
|
94
|
+
self.value = item.css("dd span").text.strip
|
95
|
+
end
|
96
|
+
|
97
|
+
def to_s
|
98
|
+
" #{name} #{value.colorize(:green)}"
|
99
|
+
end
|
100
|
+
end
|
data/lib/pizza.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class Pizzas < Array
|
3
|
+
def self.from(source)
|
4
|
+
doc = Nokogiri::HTML(source)
|
5
|
+
|
6
|
+
Pizzas.new(
|
7
|
+
doc.css(".jso-dataLayerProductClick").map { |el| Pizza.new(el) }.select(&:valid?)
|
8
|
+
)
|
9
|
+
end
|
10
|
+
|
11
|
+
def selection_list
|
12
|
+
map(&:list_item)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Pizza
|
17
|
+
attr_accessor :id, :category_id, :url, :name, :description, :allergen_warning
|
18
|
+
attr_accessor :size, :crust
|
19
|
+
|
20
|
+
def initialize(element)
|
21
|
+
return unless element["iname"]
|
22
|
+
|
23
|
+
link = element["href"]
|
24
|
+
parts = link.split("/")
|
25
|
+
pizza_id = parts.pop
|
26
|
+
category_id = parts.pop
|
27
|
+
# some_other_number = parts.pop # TODO: figure out what this is
|
28
|
+
|
29
|
+
description = element.css(".menu_itemList_item_text").first
|
30
|
+
description = description.text if description
|
31
|
+
|
32
|
+
allergen_warning = element.css(".js-menuSetHeight_allergen").first
|
33
|
+
allergen_warning = allergen_warning.text if allergen_warning
|
34
|
+
|
35
|
+
self.url = "https://order.dominos.jp#{link}"
|
36
|
+
self.id = pizza_id # shohinC1
|
37
|
+
self.category_id = category_id # categoryC
|
38
|
+
self.name = element["iname"]
|
39
|
+
self.description = description
|
40
|
+
self.allergen_warning = allergen_warning
|
41
|
+
end
|
42
|
+
|
43
|
+
def valid?
|
44
|
+
url != nil
|
45
|
+
end
|
46
|
+
|
47
|
+
def available_sizes
|
48
|
+
@available_sizes ||=
|
49
|
+
detail_page_content.css("#detail_selectSize .m-input__radio").map do |option|
|
50
|
+
Pizza::Size.new(option)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def available_crusts
|
55
|
+
@available_crusts ||=
|
56
|
+
detail_page_content.css("#detail_selectCrust .m-input__radio").map do |option|
|
57
|
+
Pizza::Crust.new(option)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def params
|
62
|
+
{
|
63
|
+
"shohinC1" => id,
|
64
|
+
"categoryC" => category_id,
|
65
|
+
"sizeC" => size.value,
|
66
|
+
"crustC" => crust.value
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
def list_item
|
71
|
+
allergen = allergen_warning || ""
|
72
|
+
|
73
|
+
"#{name.colorize(:blue)} "\
|
74
|
+
"#{allergen.strip.colorize(:yellow)}\n "\
|
75
|
+
"#{description.gsub(",", ", ").gsub(")", ") ")}\n"
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def detail_page_content
|
81
|
+
@detail_page_content ||= Nokogiri::HTML(
|
82
|
+
Request.get(url, expect: :ok, failure: "Couldn't open pizza detail page").body
|
83
|
+
)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class Pizza
|
88
|
+
class Size
|
89
|
+
attr_accessor :text, :value
|
90
|
+
|
91
|
+
def initialize(option)
|
92
|
+
self.text = option.text.strip
|
93
|
+
self.value = option.css("input[name=sizeC]").first["value"]
|
94
|
+
end
|
95
|
+
|
96
|
+
def list_item
|
97
|
+
text.gsub(/\s+/, " ").sub("δΊΊ /", "δΊΊ/").sub("cm ", "cm\n ").sub(" Β₯", "\n Β₯").strip
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class Crust
|
102
|
+
attr_accessor :text, :value
|
103
|
+
|
104
|
+
def initialize(option)
|
105
|
+
self.text = option.css(".caption_radio").first.text.strip
|
106
|
+
self.value = option.css("input[name=crustC]").first["value"]
|
107
|
+
end
|
108
|
+
|
109
|
+
def list_item
|
110
|
+
text.gsub(/\s+/, " ").strip
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class PizzaSelector
|
3
|
+
def self.select_pizzas
|
4
|
+
response = Request.get("https://order.dominos.jp/eng/pizza/search/",
|
5
|
+
expect: :ok, failure: "Couldn't get pizza list page")
|
6
|
+
|
7
|
+
pizzas = Pizzas.from(response.body)
|
8
|
+
|
9
|
+
cli = HighLine.new
|
10
|
+
choices = pizzas.selection_list
|
11
|
+
|
12
|
+
loop do
|
13
|
+
puts "-" * 42
|
14
|
+
cli.choose do |menu|
|
15
|
+
menu.prompt = "Add a pizza via number:"
|
16
|
+
menu.choices(*(choices + ["Cancel"])) do |choice|
|
17
|
+
index = choices.index(choice)
|
18
|
+
|
19
|
+
if index && index < choices.count
|
20
|
+
selected_pizza = pizzas[index]
|
21
|
+
add_pizza(customize_pizza(selected_pizza))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
menu.default = "Cancel"
|
25
|
+
end
|
26
|
+
|
27
|
+
break unless Ask.confirm "Add another pizza?"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.customize_pizza(pizza)
|
32
|
+
puts "#{"β".colorize(:green)} #{pizza.name.colorize(:blue)}"
|
33
|
+
|
34
|
+
# TODO: Allow toppings selection
|
35
|
+
|
36
|
+
# Choosing the size
|
37
|
+
selected_size_index = Ask.list "Choose the size", pizza.available_sizes.map(&:list_item)
|
38
|
+
pizza.size = pizza.available_sizes[selected_size_index]
|
39
|
+
|
40
|
+
# Choosing the crust
|
41
|
+
selected_crust_index = Ask.list "Choose the crust", pizza.available_crusts.map(&:list_item)
|
42
|
+
pizza.crust = pizza.available_crusts[selected_crust_index]
|
43
|
+
|
44
|
+
pizza
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.add_pizza(pizza)
|
48
|
+
params = pizza.params
|
49
|
+
params = params.merge(
|
50
|
+
"pageId" => "PIZZA_DETAIL",
|
51
|
+
# TODO: Allow cut type, number of slices and quantity selection
|
52
|
+
"cutTypeC" => 1, # Type of cut: 1=Round Cut
|
53
|
+
"cutSu" => 8, # Number of slices
|
54
|
+
"figure" => 1 # Quantity
|
55
|
+
)
|
56
|
+
|
57
|
+
response = Request.post(
|
58
|
+
"https://order.dominos.jp/eng/cart/add/pizza/", params,
|
59
|
+
expect: :redirect, to: %r{\Ahttps?://order\.dominos\.jp/eng/cart/added/\z},
|
60
|
+
failure: "Couldn't add the pizza you selected"
|
61
|
+
)
|
62
|
+
|
63
|
+
# For some reason we need to GET this URL otherwise it doesn't count as added <_<
|
64
|
+
Request.get(response["Location"],
|
65
|
+
expect: :redirect, to: "https://order.dominos.jp/eng/cart/",
|
66
|
+
failure: "Couldn't add the pizza you selected")
|
67
|
+
end
|
68
|
+
end
|
data/lib/preferences.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "yaml"
|
3
|
+
|
4
|
+
class Preferences
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
attr_accessor :email, :name, :phone_number, :credit_card, :note
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
preferences_path = File.join(Dir.home, ".dominosjp.yml")
|
11
|
+
return unless File.exist?(preferences_path)
|
12
|
+
|
13
|
+
prefs = YAML.safe_load(File.read(preferences_path)).map { |k, v| [(k.to_sym rescue k), v] }.to_h
|
14
|
+
|
15
|
+
self.email = prefs[:email] if prefs[:email]
|
16
|
+
self.name = prefs[:name] if prefs[:name]
|
17
|
+
self.phone_number = prefs[:phone_number] if prefs[:phone_number]
|
18
|
+
self.credit_card = CreditCard.new(prefs[:credit_card]) if prefs[:credit_card]
|
19
|
+
self.note = prefs[:note] if prefs[:note]
|
20
|
+
end
|
21
|
+
end
|
data/lib/request.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "net/http"
|
3
|
+
require "http-cookie"
|
4
|
+
require "singleton"
|
5
|
+
|
6
|
+
class Request
|
7
|
+
include Singleton
|
8
|
+
|
9
|
+
def self.get(url, options = {})
|
10
|
+
request = Net::HTTP::Get.new(URI(url))
|
11
|
+
|
12
|
+
Request.instance.perform(
|
13
|
+
request,
|
14
|
+
block_given? ? options.merge(proc: proc { |res| yield(res) }) : options
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.post(url, form_data, options = {})
|
19
|
+
request = Net::HTTP::Post.new(URI(url))
|
20
|
+
request.set_form_data(form_data)
|
21
|
+
|
22
|
+
Request.instance.perform(
|
23
|
+
request,
|
24
|
+
block_given? ? options.merge(proc: proc { |res| yield(res) }) : options
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
@base_uri = URI("https://order.dominos.jp/eng/")
|
30
|
+
@http = Net::HTTP.start(@base_uri.host, @base_uri.port, use_ssl: true)
|
31
|
+
@jar = HTTP::CookieJar.new
|
32
|
+
end
|
33
|
+
|
34
|
+
def perform(request, options)
|
35
|
+
request["Cookie"] = cookies_value
|
36
|
+
response = @http.request(request)
|
37
|
+
|
38
|
+
save_cookies(response)
|
39
|
+
parse_options(options, response)
|
40
|
+
|
41
|
+
response
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def cookies_value
|
47
|
+
HTTP::Cookie.cookie_value(@jar.cookies(@base_uri))
|
48
|
+
end
|
49
|
+
|
50
|
+
def save_cookies(response)
|
51
|
+
response.get_fields("Set-Cookie").each do |value|
|
52
|
+
@jar.parse(value, @base_uri)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def parse_options(options, response)
|
57
|
+
validate_status(options, response) if options[:expect]
|
58
|
+
validate_block(options, response) if options[:proc]
|
59
|
+
end
|
60
|
+
|
61
|
+
def validate_status(options, response)
|
62
|
+
expectation, redirect, failure = options.values_at(:expect, :to, :failure)
|
63
|
+
expectation = { ok: 200, redirect: 302 }[expectation] if expectation.is_a?(Symbol)
|
64
|
+
|
65
|
+
correct_status = (response.code.to_i == expectation)
|
66
|
+
correct_location = redirect.nil? ||
|
67
|
+
(redirect == response["Location"]) ||
|
68
|
+
(redirect.is_a?(Regexp) && redirect.match(response["Location"]))
|
69
|
+
|
70
|
+
unless correct_status && correct_location
|
71
|
+
failure_message =
|
72
|
+
failure ||
|
73
|
+
"Expected a different server response. "\
|
74
|
+
"(expected: #{options} / actual: #{response.code}[#{response["Location"]}])"
|
75
|
+
|
76
|
+
puts failure_message.colorize(:red)
|
77
|
+
raise failure_message
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def validate_block(options, response)
|
82
|
+
expectation, failure = options.values_at(:proc, :failure)
|
83
|
+
|
84
|
+
unless expectation.call(response)
|
85
|
+
failure_message = failure || "Expected a different server response. "
|
86
|
+
|
87
|
+
puts failure_message.colorize(:red)
|
88
|
+
raise failure_message
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dominosjp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mahdi Bchetnia
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-02-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: colorize
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.8'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.8'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: credit_card_validations
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.4'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.4'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: highline
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.7'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.7'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: http-cookie
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.0'
|
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'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: inquirer
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.2'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.2'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: nokogiri
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.7'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.7'
|
97
|
+
description: "A ruby gem for ordering Domino's Pizza in Japan via CLI \U0001F355"
|
98
|
+
email:
|
99
|
+
- injekter@gmail.com
|
100
|
+
executables:
|
101
|
+
- dominosjp
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- LICENSE
|
106
|
+
- README.md
|
107
|
+
- bin/dominosjp
|
108
|
+
- lib/dominosjp.rb
|
109
|
+
- lib/order_address.rb
|
110
|
+
- lib/order_coupon.rb
|
111
|
+
- lib/order_information.rb
|
112
|
+
- lib/order_payment.rb
|
113
|
+
- lib/order_review.rb
|
114
|
+
- lib/pizza.rb
|
115
|
+
- lib/pizza_selector.rb
|
116
|
+
- lib/preferences.rb
|
117
|
+
- lib/request.rb
|
118
|
+
- lib/version.rb
|
119
|
+
homepage: https://github.com/inket/dominosjp
|
120
|
+
licenses:
|
121
|
+
- MIT
|
122
|
+
metadata: {}
|
123
|
+
post_install_message:
|
124
|
+
rdoc_options: []
|
125
|
+
require_paths:
|
126
|
+
- lib
|
127
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: 1.3.6
|
137
|
+
requirements: []
|
138
|
+
rubyforge_project: dominosjp
|
139
|
+
rubygems_version: 2.5.2
|
140
|
+
signing_key:
|
141
|
+
specification_version: 4
|
142
|
+
summary: "Order Domino's Pizza Japan via CLI \U0001F355"
|
143
|
+
test_files: []
|