codes 0.4.0 → 0.4.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 +4 -4
- data/bin/codes +70 -70
- data/lib/codes.rb +1 -1
- data/lib/codes/codes_runner.rb +1 -0
- data/lib/codes/itunes_connect.rb +52 -51
- data/lib/codes/version.rb +1 -1
- metadata +11 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5de44a06df82ccf5f52fb7012beb8fbb45ea091f
|
4
|
+
data.tar.gz: 3815b7cafc3affe3801c642b72227326d6d89af9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 12a724b931a13c7d6a04a4f5fa6370fd6bd5a2d67d252e4bad9619b0e68aa36c3929bac652267577f70c94706ec43e8360f6ff6361f58737e24d03a48df585c4
|
7
|
+
data.tar.gz: 22e7597ee1a3f194f4e4943432f682e0022aa956344fc25196863d685dd3de8bd8cdd0a270cbb397cc55b7ffc86b76870b6678ff2933de427fd36119cdff7bb9
|
data/bin/codes
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
$:.push File.expand_path(
|
3
|
+
$:.push File.expand_path('../../lib', __FILE__)
|
4
4
|
|
5
5
|
require 'codes'
|
6
6
|
require 'commander'
|
@@ -17,6 +17,43 @@ class CodesApplication
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def run
|
20
|
+
prepare_command
|
21
|
+
|
22
|
+
command :download do |c|
|
23
|
+
c.syntax = 'codes download [num]'
|
24
|
+
c.description = 'Download [num] new promo codes from iTunes Connect'
|
25
|
+
|
26
|
+
c.action do |args, options|
|
27
|
+
username options
|
28
|
+
number_of_codes = count(args)
|
29
|
+
apple_id = apple_id(options)
|
30
|
+
app_identifier = bundle_id(options, apple_id)
|
31
|
+
format = download_format options
|
32
|
+
|
33
|
+
Codes::CodesRunner.download(number_of_codes: number_of_codes, app_identifier: app_identifier, apple_id: apple_id, output_file_path: options.output_file, format: format, country: options.country)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
command :display do |c|
|
38
|
+
c.syntax = 'codes display'
|
39
|
+
c.description = 'Display remaining number of promo codes from iTunes Connect'
|
40
|
+
|
41
|
+
c.action do |args, options|
|
42
|
+
username options
|
43
|
+
apple_id = apple_id(options)
|
44
|
+
app_identifier = bundle_id(options, apple_id)
|
45
|
+
|
46
|
+
Codes::CodesRunner.display(app_identifier: app_identifier, apple_id: apple_id, output_file_path: options.output_file, country: options.country)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
default_command :download
|
50
|
+
|
51
|
+
run!
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def prepare_command
|
20
57
|
program :version, Codes::VERSION
|
21
58
|
program :description, 'CLI for \'codes\' - Create promo codes for iOS Apps using the command line'
|
22
59
|
program :help, 'Author', 'Felix Krause <codes@krausefx.com>'
|
@@ -34,85 +71,48 @@ class CodesApplication
|
|
34
71
|
global_option '-f', '--format STRING', 'If specified, modifies the format of the output to the specified format'
|
35
72
|
global_option '-X', '--verbose', "If specified, defines the default output. Similar to -f \'#{download_verbose_format}\'"
|
36
73
|
global_option '-x', '--urls', 'If specified, includes a full URL for each code that can be used to redeem that code.'
|
74
|
+
end
|
37
75
|
|
76
|
+
def count(args)
|
77
|
+
[args[0].to_i, 1].max # minimum of 1 code
|
78
|
+
end
|
38
79
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
c.action do |args, options|
|
44
|
-
user = username options
|
45
|
-
number_of_codes = count(args)
|
46
|
-
apple_id = apple_id(options)
|
47
|
-
app_identifier = bundle_id(options, apple_id)
|
48
|
-
format = download_format options
|
49
|
-
|
50
|
-
Codes::CodesRunner.download(number_of_codes: number_of_codes, app_identifier: app_identifier, apple_id: apple_id, output_file_path: options.output_file, format: format, country: options.country)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
command :display do |c|
|
55
|
-
c.syntax = "codes display"
|
56
|
-
c.description = "Display remaining number of promo codes from iTunes Connect"
|
57
|
-
|
58
|
-
c.action do |args, options|
|
59
|
-
user = username options
|
60
|
-
apple_id = apple_id(options)
|
61
|
-
app_identifier = bundle_id(options, apple_id)
|
62
|
-
|
63
|
-
Codes::CodesRunner.display(app_identifier: app_identifier, apple_id: apple_id, output_file_path: options.output_file, country: options.country)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
default_command :download
|
67
|
-
|
68
|
-
def count(args)
|
69
|
-
[args[0].to_i, 1].max # minimum of 1 code
|
70
|
-
end
|
71
|
-
|
72
|
-
def username(options)
|
73
|
-
user = options.username
|
74
|
-
user ||= ENV["CODES_USERNAME"]
|
75
|
-
user ||= CredentialsManager::AppfileConfig.try_fetch_value(:apple_id)
|
76
|
-
|
77
|
-
|
78
|
-
CredentialsManager::PasswordManager.shared_manager(user) if user
|
80
|
+
def username(options)
|
81
|
+
user = options.username
|
82
|
+
user ||= ENV['CODES_USERNAME']
|
83
|
+
user ||= CredentialsManager::AppfileConfig.try_fetch_value(:apple_id)
|
79
84
|
|
80
|
-
|
81
|
-
end
|
85
|
+
CredentialsManager::PasswordManager.shared_manager(user) if user
|
82
86
|
|
83
|
-
|
84
|
-
|
85
|
-
urls = options.x || options.urls
|
86
|
-
format = options.f || options.format
|
87
|
-
# backward compatibility...
|
88
|
-
if urls && !format
|
89
|
-
format = "%c - %u"
|
90
|
-
end
|
91
|
-
if verbose && !format
|
92
|
-
format = download_verbose_format
|
93
|
-
end
|
94
|
-
format
|
95
|
-
end
|
87
|
+
user
|
88
|
+
end
|
96
89
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
90
|
+
def download_format(options)
|
91
|
+
verbose = options.X || options.verbose
|
92
|
+
urls = options.x || options.urls
|
93
|
+
format = options.f || options.format
|
94
|
+
# backward compatibility...
|
95
|
+
format = '%c - %u' if urls && !format
|
96
|
+
format = download_verbose_format if verbose && !format
|
97
|
+
format
|
98
|
+
end
|
105
99
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
100
|
+
def bundle_id(options, apple_id)
|
101
|
+
app_identifier = options.app_identifier
|
102
|
+
app_identifier ||= ENV['CODES_APP_IDENTIFIER']
|
103
|
+
app_identifier ||= CredentialsManager::AppfileConfig.try_fetch_value(:app_identifier)
|
104
|
+
app_identifier ||= (FastlaneCore::ItunesSearchApi.fetch(apple_id)['bundleId'] rescue nil) if apple_id
|
105
|
+
app_identifier ||= ask('App Identifier (Bundle ID, e.g. com.krausefx.app): ')
|
106
|
+
app_identifier
|
107
|
+
end
|
110
108
|
|
111
|
-
|
109
|
+
def apple_id(options)
|
110
|
+
apple_id = options.apple_id
|
111
|
+
apple_id ||= ENV['CODES_APP_ID']
|
112
|
+
apple_id
|
112
113
|
end
|
113
114
|
end
|
114
115
|
|
115
|
-
|
116
116
|
begin
|
117
117
|
FastlaneCore::UpdateChecker.start_looking_for_update('codes')
|
118
118
|
CodesApplication.new.run
|
data/lib/codes.rb
CHANGED
data/lib/codes/codes_runner.rb
CHANGED
data/lib/codes/itunes_connect.rb
CHANGED
@@ -2,30 +2,30 @@ require 'fastlane_core/itunes_connect/itunes_connect'
|
|
2
2
|
|
3
3
|
module Codes
|
4
4
|
class ItunesConnect < FastlaneCore::ItunesConnect
|
5
|
+
PROMO_URL = 'https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/wa/LCAppPage/viewPromoCodes?adamId=[[app_id]]&platform=[[platform]]'
|
6
|
+
CODE_URL = 'https://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/redeemLandingPage?code=[[code]]'
|
5
7
|
|
6
|
-
|
7
|
-
CODE_URL = "https://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/redeemLandingPage?code=[[code]]"
|
8
|
-
|
8
|
+
# rubocop:disable Metrics/AbcSize
|
9
9
|
def download(args)
|
10
10
|
number_of_codes = args[:number_of_codes]
|
11
11
|
|
12
|
-
code_or_codes = number_of_codes == 1 ?
|
13
|
-
Helper.log.info "Downloading #{number_of_codes} promo #{code_or_codes}..."
|
12
|
+
code_or_codes = number_of_codes == 1 ? 'code' : 'codes'
|
13
|
+
Helper.log.info "Downloading #{number_of_codes} promo #{code_or_codes}..."
|
14
14
|
|
15
15
|
fetch_app_data args
|
16
16
|
|
17
17
|
# Use Pathname because it correctly handles the distinction between relative paths vs. absolute paths
|
18
18
|
output_file_path = Pathname.new(args[:output_file_path]) if args[:output_file_path]
|
19
19
|
output_file_path ||= Pathname.new(File.join(Dir.getwd, "#{@app_identifier || @app_id}_codes.txt"))
|
20
|
-
|
21
|
-
visit PROMO_URL.gsub(
|
20
|
+
fail 'Insufficient permissions to write to output file'.red if File.exist?(output_file_path) && !File.writable?(output_file_path)
|
21
|
+
visit PROMO_URL.gsub('[[app_id]]', @app_id.to_s).gsub('[[platform]]', @platform)
|
22
22
|
|
23
23
|
begin
|
24
|
-
text_fields = wait_for_elements(
|
24
|
+
text_fields = wait_for_elements('input[type=text]')
|
25
25
|
rescue
|
26
26
|
raise "Could not open details page for app #{@app_identifier}. Are you sure you are using the correct apple account and have access to this app?".red
|
27
27
|
end
|
28
|
-
|
28
|
+
fail 'There should only be a single text input field to specify the number of codes'.red unless text_fields.count == 1
|
29
29
|
|
30
30
|
text_fields.first.set(number_of_codes.to_s)
|
31
31
|
click_next
|
@@ -33,83 +33,84 @@ module Codes
|
|
33
33
|
# are there any errors ?
|
34
34
|
errors = []
|
35
35
|
begin
|
36
|
-
|
37
|
-
rescue
|
36
|
+
errors = wait_for_elements('div[id=LCPurpleSoftwarePageWrapperErrorMessage]')
|
37
|
+
rescue
|
38
38
|
end
|
39
|
-
|
39
|
+
fail errors.first.text.red unless errors.count == 0
|
40
40
|
|
41
|
-
Helper.log.debug
|
42
|
-
wait_for_elements(
|
41
|
+
Helper.log.debug 'Accepting the App Store Volume Custom Code Agreement'
|
42
|
+
wait_for_elements('input[type=checkbox]').first.click
|
43
43
|
click_next
|
44
44
|
|
45
45
|
# the find(:xpath, "..") gets the parent element of the previous expression
|
46
46
|
download_url = wait_for_elements("div[class='large-blue-rect-button']").first.find(:xpath, '..')['href']
|
47
47
|
|
48
48
|
codes, request_date = download_codes(download_url)
|
49
|
-
|
49
|
+
|
50
50
|
format = args[:format]
|
51
51
|
codes = download_format(codes, format, request_date, app) if format
|
52
52
|
|
53
|
-
bytes_written = File.write(output_file_path.to_s, codes, mode:
|
54
|
-
Helper.log.warn
|
55
|
-
Helper.log.info "Added generated codes to '#{output_file_path
|
53
|
+
bytes_written = File.write(output_file_path.to_s, codes, mode: 'a+')
|
54
|
+
Helper.log.warn 'Could not write your codes to the codes.txt file, but you can still access them from iTunes Connect later' if bytes_written == 0
|
55
|
+
Helper.log.info "Added generated codes to '#{output_file_path}'".green unless bytes_written == 0
|
56
56
|
|
57
57
|
Helper.log.info "Your codes (requested #{request_date}) were successfully downloaded:".green
|
58
58
|
puts codes
|
59
59
|
end
|
60
|
+
# rubocop:enable Metrics/AbcSize
|
60
61
|
|
61
62
|
def download_format(codes, format, request_date, app)
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
63
|
+
format = format.gsub(/%([a-z])/, '%{\\1}') # %c => %{c}
|
64
|
+
|
65
|
+
codes = codes.split("\n").map do |code|
|
66
|
+
format % {
|
67
|
+
c: code,
|
68
|
+
b: app['bundleId'],
|
69
|
+
d: request_date, # e.g. 20150520110716 / Cupertino timestamp...
|
70
|
+
i: app['trackId'],
|
71
|
+
n: "\'#{app['trackName']}\'",
|
72
|
+
p: app_platform(app),
|
73
|
+
u: CODE_URL.gsub('[[code]]', code)
|
74
|
+
}
|
75
|
+
end
|
76
|
+
codes.join("\n") + "\n"
|
76
77
|
end
|
77
78
|
|
78
79
|
def app_platform(app)
|
79
|
-
app['kind'] ==
|
80
|
+
app['kind'] == 'mac-software' ? 'osx' : 'ios'
|
80
81
|
end
|
81
82
|
|
82
83
|
def display(args)
|
83
|
-
Helper.log.info
|
84
|
+
Helper.log.info 'Displaying remaining number of codes promo'
|
84
85
|
|
85
86
|
fetch_app_data args
|
86
87
|
|
87
88
|
# Use Pathname because it correctly handles the distinction between relative paths vs. absolute paths
|
88
89
|
output_file_path = Pathname.new(args[:output_file_path]) if args[:output_file_path]
|
89
90
|
output_file_path ||= Pathname.new(File.join(Dir.getwd, "#{@app_identifier || @app_id}_codes_info.txt"))
|
90
|
-
|
91
|
-
visit PROMO_URL.gsub(
|
91
|
+
fail 'Insufficient permissions to write to output file'.red if File.exist?(output_file_path) && !File.writable?(output_file_path)
|
92
|
+
visit PROMO_URL.gsub('[[app_id]]', @app_id.to_s).gsub('[[platform]]', @platform)
|
92
93
|
|
93
94
|
begin
|
94
|
-
text_fields = wait_for_elements(
|
95
|
+
text_fields = wait_for_elements('input[type=text]')
|
95
96
|
rescue
|
96
97
|
raise "Could not open details page for app #{app_identifier}. Are you sure you are using the correct apple account and have access to this app?".red
|
97
98
|
end
|
98
|
-
|
99
|
+
fail 'There should only be a single text input field to specify the number of codes'.red unless text_fields.count == 1
|
99
100
|
|
100
|
-
remaining_divs = wait_for_elements(
|
101
|
-
|
102
|
-
remaining = remaining_divs.first.text.split(
|
101
|
+
remaining_divs = wait_for_elements('div#codes_0')
|
102
|
+
fail 'There should only be a single text div containing the number of remaining codes'.red unless remaining_divs.count == 1
|
103
|
+
remaining = remaining_divs.first.text.split(' ')[0]
|
103
104
|
|
104
|
-
bytes_written = File.write(output_file_path.to_s, remaining, mode:
|
105
|
-
Helper.log.warn
|
106
|
-
Helper.log.info "Added information of quantity of remaining codes to '#{output_file_path
|
105
|
+
bytes_written = File.write(output_file_path.to_s, remaining, mode: 'a+')
|
106
|
+
Helper.log.warn 'Could not write your codes to the codes_info.txt file, but you can still access them from iTunes Connect later' if bytes_written == 0
|
107
|
+
Helper.log.info "Added information of quantity of remaining codes to '#{output_file_path}'".green unless bytes_written == 0
|
107
108
|
|
108
109
|
puts remaining
|
109
110
|
end
|
110
111
|
|
111
112
|
def click_next
|
112
|
-
wait_for_elements(
|
113
|
+
wait_for_elements('input.continueActionButton').first.click
|
113
114
|
end
|
114
115
|
|
115
116
|
def download_codes(url)
|
@@ -117,16 +118,16 @@ module Codes
|
|
117
118
|
url = URI.join(host, url)
|
118
119
|
Helper.log.debug "Downloading promo code file from #{url}"
|
119
120
|
|
120
|
-
cookie_string =
|
121
|
+
cookie_string = ''
|
121
122
|
page.driver.cookies.each do |key, cookie|
|
122
123
|
cookie_string << "#{cookie.name}=#{cookie.value};"
|
123
124
|
end
|
124
125
|
|
125
|
-
page = open(url,
|
126
|
-
request_date = page.metas['content-disposition'][0].gsub(/.*filename=.*_(.*).txt/,
|
126
|
+
page = open(url, 'Cookie' => cookie_string)
|
127
|
+
request_date = page.metas['content-disposition'][0].gsub(/.*filename=.*_(.*).txt/, '\\1')
|
127
128
|
codes = page.read
|
128
129
|
|
129
|
-
|
130
|
+
[codes, request_date]
|
130
131
|
end
|
131
132
|
|
132
133
|
private
|
@@ -138,8 +139,8 @@ module Codes
|
|
138
139
|
@app_id = args[:apple_id]
|
139
140
|
@app_id ||= (FastlaneCore::ItunesSearchApi.fetch_by_identifier(@app_identifier, @country)['trackId'] rescue nil)
|
140
141
|
|
141
|
-
if @app_id.to_i == 0
|
142
|
-
|
142
|
+
if @app_id.to_i == 0 || @app_identifier.to_s.length == 0
|
143
|
+
fail "Could not find app using the following information: #{args}. Maybe the app is not in the store. Pass the Apple ID of the app as well!".red
|
143
144
|
end
|
144
145
|
|
145
146
|
app = FastlaneCore::ItunesSearchApi.fetch(@app_id, @country)
|
data/lib/codes/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: codes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Felix Krause
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-09-
|
11
|
+
date: 2015-09-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fastlane_core
|
@@ -16,14 +16,20 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.16.0
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.0.0
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
27
|
- - ">="
|
25
28
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.
|
29
|
+
version: 0.16.0
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.0.0
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: bundler
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -158,7 +164,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
158
164
|
version: '0'
|
159
165
|
requirements: []
|
160
166
|
rubyforge_project:
|
161
|
-
rubygems_version: 2.4.
|
167
|
+
rubygems_version: 2.4.6
|
162
168
|
signing_key:
|
163
169
|
specification_version: 4
|
164
170
|
summary: Create promo codes for iOS Apps using the command line
|