codes 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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