codes 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 344a3981e1ce1283a03aff8765eee7bdd693d9c9
4
- data.tar.gz: e2a579552a9263931bffecd487d84c20306a8c81
3
+ metadata.gz: 34b56b7948df3498826c2deed30e0b559ee3b0ae
4
+ data.tar.gz: 8c1e4e68aaaae4125efefcc4a7e57e88927ec011
5
5
  SHA512:
6
- metadata.gz: 1b6877cc4bd78af8094c5e72c66f299c711e275f6f0e6fb476d1d2c7c81d660dcbe749374a4b824261a1a0a4359c00e7d122893e339252d4e4157fef3c66dd29
7
- data.tar.gz: fd65d6ff3df01fc1db381c417b1554605bda178c96be77025470131f02875dc3fc94630108e6895a9e46ff2f9fd0fba32817c5ebb2f55f8db501308a7b45800b
6
+ metadata.gz: aa064ec5e1229d765f377227dd26f138e89e8fdcf8a48ebae235dbec31a4eb19e0ae65a9034097dfe9d347ffd56c4cd5793355e0b35831c76257cb75369e3f8b
7
+ data.tar.gz: 45e3a769ce73b7ac8fa38e18fa2853d39fd77b10fe42102debb1790e76b12bbca30ee31285085e58634deffc45613906565e565fe68226b09fe8862a1f9d36ff
data/README.md CHANGED
@@ -70,7 +70,7 @@ Make sure, you have the latest version of the Xcode command line tools installed
70
70
 
71
71
  # Usage
72
72
 
73
- codes [num] [-a app_identifier] [-u user_name] [-i app_id] [-o output_file]
73
+ codes [num] [-a app_identifier] [-u user_name] [-i app_id] [-o output_file] [-c country] [-X] [-f format]
74
74
 
75
75
  All parameters are optional.
76
76
 
@@ -84,6 +84,24 @@ Will generate 3 promo codes for the the App with the Bundle Identifier `com.exam
84
84
 
85
85
  If you don't pass any paramaters, `codes` will generate a single promo code and print it on the command line.
86
86
 
87
+ ## Display extra information
88
+
89
+ Use the ```--format FORMAT``` argument to control the displayed information. The ```--verbose``` argument displays all information and maps to
90
+
91
+ --format '%c,%d,%p,%i,%b,%n,%u'
92
+
93
+ E.g.
94
+
95
+ codes --verbose
96
+
97
+ will display
98
+
99
+ NRTFXP3XXXXX,20150520110716,ios,522069155,com.wewanttoknow.DragonBoxPlus,'DragonBox Algebra 5+',https://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/redeemLandingPage?code=NRTFXP3XXXXX
100
+
101
+ For example, if you want a custom URL, you could use something like
102
+
103
+ codes --format '%c: http://yoursite.com/redeem/%c'
104
+
87
105
  ## Generate URLs
88
106
 
89
107
  codes 5 --urls
data/bin/codes CHANGED
@@ -12,6 +12,10 @@ HighLine.track_eof = false
12
12
  class CodesApplication
13
13
  include Commander::Methods
14
14
 
15
+ def download_verbose_format
16
+ '%c,%d,%p,%i,%b,%n,%u'
17
+ end
18
+
15
19
  def run
16
20
  program :version, Codes::VERSION
17
21
  program :description, 'CLI for \'codes\' - Create promo codes for iOS Apps using the command line'
@@ -27,6 +31,8 @@ class CodesApplication
27
31
  global_option '-a', '--app_identifier STRING', 'The bundle ID to download promo codes for'
28
32
  global_option '-i', '--apple_id STRING', 'The App ID to download promo codes for'
29
33
  global_option '-o', '--output_file STRING', 'The name of the file that should be written to disk. If the file exists, the new codes will be appended.'
34
+ global_option '-f', '--format STRING', 'If specified, modifies the format of the output to the specified format'
35
+ global_option '-X', '--verbose', "If specified, defines the default output. Similar to -f \'#{download_verbose_format}\'"
30
36
  global_option '-x', '--urls', 'If specified, includes a full URL for each code that can be used to redeem that code.'
31
37
 
32
38
 
@@ -39,12 +45,24 @@ class CodesApplication
39
45
  number_of_codes = count(args)
40
46
  apple_id = apple_id(options)
41
47
  app_identifier = bundle_id(options, apple_id)
42
- urls = options.x || options.urls
48
+ format = download_format options
43
49
 
44
- Codes::CodesRunner.run(number_of_codes: number_of_codes, app_identifier: app_identifier, apple_id: apple_id, output_file_path: options.output_file, urls: urls, country: options.country)
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)
45
51
  end
46
52
  end
47
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
48
66
  default_command :download
49
67
 
50
68
  def count(args)
@@ -62,6 +80,20 @@ class CodesApplication
62
80
  user
63
81
  end
64
82
 
83
+ def download_format(options)
84
+ verbose = options.X || options.verbose
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
96
+
65
97
  def bundle_id(options, apple_id)
66
98
  app_identifier = options.app_identifier
67
99
  app_identifier ||= ENV["CODES_APP_IDENTIFIER"]
@@ -1,7 +1,10 @@
1
1
  module Codes
2
2
  class CodesRunner
3
- def self.run(args)
4
- Codes::ItunesConnect.new.run args
3
+ def self.download(args)
4
+ Codes::ItunesConnect.new.download args
5
+ end
6
+ def self.display(args)
7
+ Codes::ItunesConnect.new.display args
5
8
  end
6
9
  end
7
10
  end
@@ -6,41 +6,38 @@ module Codes
6
6
  PROMO_URL = "https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/wa/LCAppPage/viewPromoCodes?adamId=[[app_id]]&platform=[[platform]]"
7
7
  CODE_URL = "https://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/redeemLandingPage?code=[[code]]"
8
8
 
9
- def run(args)
9
+ def download(args)
10
10
  number_of_codes = args[:number_of_codes]
11
- country = args[:country]
12
11
 
13
12
  code_or_codes = number_of_codes == 1 ? "code" : "codes"
14
13
  Helper.log.info "Downloading #{number_of_codes} promo #{code_or_codes}..."
15
14
 
16
-
17
- app_id = args[:apple_id]
18
- app_id ||= (FastlaneCore::ItunesSearchApi.fetch_by_identifier(args[:app_identifier], country)['trackId'] rescue nil)
19
-
20
- app_identifier = args[:app_identifier]
21
-
22
- if app_id.to_i == 0 or app_identifier.to_s.length == 0
23
- raise "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
24
- end
25
-
26
- app = FastlaneCore::ItunesSearchApi.fetch(app_id, country)
27
- platform = (app['kind'] == "mac-software" ? "osx" : "ios")
15
+ fetch_app_data args
28
16
 
29
17
  # Use Pathname because it correctly handles the distinction between relative paths vs. absolute paths
30
18
  output_file_path = Pathname.new(args[:output_file_path]) if args[:output_file_path]
31
- output_file_path ||= Pathname.new(File.join(Dir.getwd, "#{app_identifier || app_id}_codes.txt"))
19
+ output_file_path ||= Pathname.new(File.join(Dir.getwd, "#{@app_identifier || @app_id}_codes.txt"))
32
20
  raise "Insufficient permissions to write to output file".red if File.exists?(output_file_path) and not File.writable?(output_file_path)
33
- visit PROMO_URL.gsub("[[app_id]]", app_id.to_s).gsub("[[platform]]", platform)
21
+ visit PROMO_URL.gsub("[[app_id]]", @app_id.to_s).gsub("[[platform]]", @platform)
34
22
 
35
23
  begin
36
24
  text_fields = wait_for_elements("input[type=text]")
37
25
  rescue
38
- 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
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
39
27
  end
40
28
  raise "There should only be a single text input field to specify the number of codes".red unless text_fields.count == 1
41
29
 
42
30
  text_fields.first.set(number_of_codes.to_s)
43
31
  click_next
32
+
33
+ # are there any errors ?
34
+ errors = []
35
+ begin
36
+ errors = wait_for_elements("div[id=LCPurpleSoftwarePageWrapperErrorMessage]")
37
+ rescue Exception
38
+ end
39
+ raise errors.first.text.red unless errors.count == 0
40
+
44
41
  Helper.log.debug "Accepting the App Store Volume Custom Code Agreement"
45
42
  wait_for_elements("input[type=checkbox]").first.click
46
43
  click_next
@@ -48,23 +45,69 @@ module Codes
48
45
  # the find(:xpath, "..") gets the parent element of the previous expression
49
46
  download_url = wait_for_elements("div[class='large-blue-rect-button']").first.find(:xpath, '..')['href']
50
47
 
51
- codes = download_codes download_url
48
+ codes, request_date = download_codes(download_url)
52
49
 
53
- if args[:urls]
54
- codes = codes.split("\n").map do |code|
55
- code +" - "+ CODE_URL.gsub("[[code]]", code)
56
- end
57
- codes = codes.join("\n") + "\n"
58
- end
50
+ format = args[:format]
51
+ codes = download_format(codes, format, request_date, app) if format
59
52
 
60
53
  bytes_written = File.write(output_file_path.to_s, codes, mode: "a+")
61
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
62
55
  Helper.log.info "Added generated codes to '#{output_file_path.to_s}'".green unless bytes_written == 0
63
56
 
64
- Helper.log.info "Your codes were successfully downloaded:".green
57
+ Helper.log.info "Your codes (requested #{request_date}) were successfully downloaded:".green
65
58
  puts codes
66
59
  end
67
60
 
61
+ def download_format(codes, format, request_date, app)
62
+ format = format.gsub(/%([a-z])/, "%{\\1}") # %c => %{c}
63
+
64
+ codes = codes.split("\n").map do |code|
65
+ format % {
66
+ c: code,
67
+ b: app['bundleId'],
68
+ d: request_date, # e.g. 20150520110716 / Cupertino timestamp...
69
+ i: app['trackId'],
70
+ n: "\'#{app['trackName']}\'",
71
+ p: app_platform(app),
72
+ u: CODE_URL.gsub("[[code]]", code)
73
+ }
74
+ end
75
+ codes = codes.join("\n") + "\n"
76
+ end
77
+
78
+ def app_platform(app)
79
+ app['kind'] == "mac-software" ? "osx" : "ios"
80
+ end
81
+
82
+ def display(args)
83
+ Helper.log.info "Displaying remaining number of codes promo"
84
+
85
+ fetch_app_data args
86
+
87
+ # Use Pathname because it correctly handles the distinction between relative paths vs. absolute paths
88
+ output_file_path = Pathname.new(args[:output_file_path]) if args[:output_file_path]
89
+ output_file_path ||= Pathname.new(File.join(Dir.getwd, "#{@app_identifier || @app_id}_codes_info.txt"))
90
+ raise "Insufficient permissions to write to output file".red if File.exists?(output_file_path) and not File.writable?(output_file_path)
91
+ visit PROMO_URL.gsub("[[app_id]]", @app_id.to_s).gsub("[[platform]]", @platform)
92
+
93
+ begin
94
+ text_fields = wait_for_elements("input[type=text]")
95
+ rescue
96
+ 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
+ end
98
+ raise "There should only be a single text input field to specify the number of codes".red unless text_fields.count == 1
99
+
100
+ remaining_divs = wait_for_elements("div#codes_0")
101
+ raise "There should only be a single text div containing the number of remaining codes".red unless remaining_divs.count == 1
102
+ remaining = remaining_divs.first.text.split(" ")[0]
103
+
104
+ bytes_written = File.write(output_file_path.to_s, remaining, mode: "a+")
105
+ 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
106
+ Helper.log.info "Added information of quantity of remaining codes to '#{output_file_path.to_s}'".green unless bytes_written == 0
107
+
108
+ puts remaining
109
+ end
110
+
68
111
  def click_next
69
112
  wait_for_elements("input.continueActionButton").first.click
70
113
  end
@@ -79,8 +122,28 @@ module Codes
79
122
  cookie_string << "#{cookie.name}=#{cookie.value};"
80
123
  end
81
124
 
82
- open(url, "Cookie" => cookie_string).read
125
+ page = open(url, "Cookie" => cookie_string)
126
+ request_date = page.metas['content-disposition'][0].gsub(/.*filename=.*_(.*).txt/,"\\1")
127
+ codes = page.read
128
+
129
+ return codes, request_date
83
130
  end
84
131
 
132
+ private
133
+
134
+ def fetch_app_data(args)
135
+ @country = args[:country]
136
+ @app_identifier = args[:app_identifier]
137
+
138
+ @app_id = args[:apple_id]
139
+ @app_id ||= (FastlaneCore::ItunesSearchApi.fetch_by_identifier(@app_identifier, @country)['trackId'] rescue nil)
140
+
141
+ if @app_id.to_i == 0 or @app_identifier.to_s.length == 0
142
+ raise "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
+ end
144
+
145
+ app = FastlaneCore::ItunesSearchApi.fetch(@app_id, @country)
146
+ @platform = app_platform app
147
+ end
85
148
  end
86
149
  end
@@ -1,3 +1,3 @@
1
1
  module Codes
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
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.3.0
4
+ version: 0.4.0
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-02 00:00:00.000000000 Z
11
+ date: 2015-09-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fastlane_core