itunes_transporter_generator 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +169 -0
- data/Rakefile +1 -0
- data/bin/itmsp +16 -0
- data/example/achievements.rb +11 -0
- data/example/config.yaml +4 -0
- data/example/in_app_purchases.rb +9 -0
- data/example/leaderboards.rb +12 -0
- data/example/test.jpg +0 -0
- data/example/test.rb +5 -0
- data/itmsp.gemspec +16 -0
- data/lib/itunes.rb +3 -0
- data/lib/itunes/transporter/achievement.rb +9 -0
- data/lib/itunes/transporter/commands.rb +2 -0
- data/lib/itunes/transporter/commands/package.rb +13 -0
- data/lib/itunes/transporter/generator.rb +196 -0
- data/lib/itunes/transporter/helpers.rb +143 -0
- data/lib/itunes/transporter/in_app_purchase.rb +28 -0
- data/lib/itunes/transporter/leaderboard.rb +36 -0
- data/lib/itunes/transporter/locale.rb +13 -0
- metadata +81 -0
data/README.md
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
# itunes_transporter_generator
|
2
|
+
**CLI for generating and packaging app store assets for Game Center and In-App Purchases**
|
3
|
+
|
4
|
+
Apple recently released their Transporter app (http://bit.ly/UcEhAh) which is a handy way to manage your App Store Packages (.itmsp) instead of having to input achievements, leaderboards, and in-app purchases through iTunes Connect. Unfortunately, there isn't an easy way to generate these packages. Why write XML by hand and deal with the hassle of calculating MD5 and file sizes? Why not define your Game Center and In-App Purchases in a simple format and have your App Store Package generated for you?
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
```sh
|
8
|
+
$ gem install itunes_transporter_generator
|
9
|
+
```
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
### Get Familiar With The Spec
|
14
|
+
First, you'll need to be familiar with the app metadata specification (download from [here](http://bit.ly/TtHMF6)), which outlines all the possible information describing an achievement, leaderboard, or in-app purchase.
|
15
|
+
|
16
|
+
### Describe your application metadata
|
17
|
+
Next, you'll need to create a YAML file describing your company information and achievement, leaderboard, and/or in-app purchase details.
|
18
|
+
|
19
|
+
### Generate the .itmsp
|
20
|
+
This is as simple as running the following command in the folder containing your YAML config file and all referenced images.
|
21
|
+
|
22
|
+
```sh
|
23
|
+
$ itmsp package -i <configuration file>
|
24
|
+
```
|
25
|
+
|
26
|
+
### That's It!
|
27
|
+
You can now upload your .itmsp package to iTunes Connect using the Transporter tool. Alternatively, there is another great open-source tool which wraps some of the complexities of running Transporter directly (https://github.com/sshaw/itunes_store_transporter)
|
28
|
+
|
29
|
+
## Sample Config file
|
30
|
+
Below is a sample configuration file describing an achievement, leaderboard, and in-app purchase. The specific metadata required for each item type is fully described in the [App Metadata Specification guide](http://bit.ly/TtHMF6)
|
31
|
+
|
32
|
+
```yaml
|
33
|
+
provider: SampleCompany # optional if team_id is supplied
|
34
|
+
team_id: ABCDE12345 # optional if provider is supplied
|
35
|
+
vendor_id: sample sku # the application's SKU as definied in iTunes Connect
|
36
|
+
id_prefix: com.samplecompany.applicationname. # if supplied, this will be prefixed to achievement, leaderboard, and in-app purchase IDs
|
37
|
+
achievements: # the order in which achievements are defined will be the order in which they appear in Game Center
|
38
|
+
- id: first_achievement
|
39
|
+
name: First Achievement
|
40
|
+
points: 10
|
41
|
+
locales:
|
42
|
+
- name: en
|
43
|
+
title: First Achievement
|
44
|
+
before_earned_description: 'Complete a task'
|
45
|
+
after_earned_description: 'Completed a task'
|
46
|
+
after_earned_image: achievement.jpg
|
47
|
+
|
48
|
+
leaderboards: # the order in which leaderboards are defined will be the order in which they appear in Game Center
|
49
|
+
- default: true # only one leaderboard can be set as default.
|
50
|
+
id: top_scores
|
51
|
+
name: Top Scores
|
52
|
+
locales:
|
53
|
+
- name: en
|
54
|
+
title: Top Scores
|
55
|
+
formatter_suffix: ' Points' # note the space. This must be provided if you want a space between the value and suffix
|
56
|
+
formatter_suffix_singular: ' Point'
|
57
|
+
formatter_type: INTEGER_COMMA_SEPARATOR
|
58
|
+
leaderboard_image: leaderboard.jpg
|
59
|
+
|
60
|
+
purchases:
|
61
|
+
- product_id: one_hundred_dollars
|
62
|
+
reference_name: '100 dollars'
|
63
|
+
type: consumable
|
64
|
+
review_screenshot_image: product1.jpg
|
65
|
+
products:
|
66
|
+
- cleared_for_sale: true
|
67
|
+
wholesale_price_tier: 1 # pricing tier matrix is available in Exhibit C of the iOS Paid Applications contract in the Contracts, Tax, and Banking section of iTunes Connect
|
68
|
+
locales:
|
69
|
+
- name: en
|
70
|
+
title: '$100'
|
71
|
+
description: 'An extra $100 to help you out'
|
72
|
+
```
|
73
|
+
|
74
|
+
This configuration will generate the following metadata.xml:
|
75
|
+
```xml
|
76
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
77
|
+
<package xmlns="http://apple.com/itunes/importer" version="software5.0">
|
78
|
+
<provider>SampleCompany</provider>
|
79
|
+
<team_id>ABCDE12345</team_id>
|
80
|
+
<software>
|
81
|
+
<vendor_id>sample sku</vendor_id>
|
82
|
+
<software_metadata>
|
83
|
+
<game_center>
|
84
|
+
<achievements>
|
85
|
+
<achievement position="1">
|
86
|
+
<achievement_id>com.samplecompany.applicationname.first_achievement</achievement_id>
|
87
|
+
<reference_name>First Achievement</reference_name>
|
88
|
+
<points>10</points>
|
89
|
+
<hidden>false</hidden>
|
90
|
+
<reusable>false</reusable>
|
91
|
+
<locales>
|
92
|
+
<locale name="en">
|
93
|
+
<title>First Achievement</title>
|
94
|
+
<before_earned_description>Complete a task</before_earned_description>
|
95
|
+
<after_earned_description>Completed a task</after_earned_description>
|
96
|
+
<after_earned_image>
|
97
|
+
<file_name>test.jpg</file_name>
|
98
|
+
<size>20092</size>
|
99
|
+
<checksum type="md5">ff5bd97a5f40bb75a84884589ecbfc42</checksum>
|
100
|
+
</after_earned_image>
|
101
|
+
</locale>
|
102
|
+
</locales>
|
103
|
+
</achievement>
|
104
|
+
</achievements>
|
105
|
+
<leaderboards>
|
106
|
+
<leaderboard default="true" position="1">
|
107
|
+
<leaderboard_id/>
|
108
|
+
<reference_name>Top Scores</reference_name>
|
109
|
+
<sort_ascending>false</sort_ascending>
|
110
|
+
<locales>
|
111
|
+
<locale name="en">
|
112
|
+
<title>Top Scores</title>
|
113
|
+
<formatter_suffix> Points</formatter_suffix>
|
114
|
+
<formatter_suffix_singular> Point</formatter_suffix_singular>
|
115
|
+
<formatter_type>INTEGER_COMMA_SEPARATOR</formatter_type>
|
116
|
+
<leaderboard_image>
|
117
|
+
<file_name>test.jpg</file_name>
|
118
|
+
<size>20092</size>
|
119
|
+
<checksum type="md5">ff5bd97a5f40bb75a84884589ecbfc42</checksum>
|
120
|
+
</leaderboard_image>
|
121
|
+
</locale>
|
122
|
+
</locales>
|
123
|
+
</leaderboard>
|
124
|
+
</leaderboards>
|
125
|
+
<in_app_purchases>
|
126
|
+
<in_app_purchase>
|
127
|
+
<product_id>com.samplecompany.applicationname.one_hundred_dollars</product_id>
|
128
|
+
<reference_name>100 dollars</reference_name>
|
129
|
+
<type>consumable</type>
|
130
|
+
<products>
|
131
|
+
<product>
|
132
|
+
<cleared_for_sale>true</cleared_for_sale>
|
133
|
+
<wholesale_price_tier>1</wholesale_price_tier>
|
134
|
+
</product>
|
135
|
+
</products>
|
136
|
+
<locales>
|
137
|
+
<locale name="en">
|
138
|
+
<title>$100</title>
|
139
|
+
<description>An extra $100 to help you out</description>
|
140
|
+
</locale>
|
141
|
+
</locales>
|
142
|
+
<review_screenshot>
|
143
|
+
<file_name>test.jpg</file_name>
|
144
|
+
<size>20092</size>
|
145
|
+
<checksum type="md5">ff5bd97a5f40bb75a84884589ecbfc42</checksum>
|
146
|
+
</review_screenshot>
|
147
|
+
</in_app_purchase>
|
148
|
+
</in_app_purchases>
|
149
|
+
</game_center>
|
150
|
+
</software_metadata>
|
151
|
+
</software>
|
152
|
+
</package>
|
153
|
+
```
|
154
|
+
|
155
|
+
## Todo
|
156
|
+
* TESTS. High on my list
|
157
|
+
* Metadata validation. There are lots of rules depending on what values you provide. You should know when you generate the package if there are any issues instead of having Apple tell you when you upload
|
158
|
+
* Anything else! Pull requests are welcome! This is my first Ruby project so I'm sure there is are a lot of improvements that can be made
|
159
|
+
|
160
|
+
## Contact
|
161
|
+
|
162
|
+
Colin Humber
|
163
|
+
|
164
|
+
- http://github.com/colinhumber
|
165
|
+
- http://twitter.com/colinhumber
|
166
|
+
|
167
|
+
## License
|
168
|
+
|
169
|
+
itmsp is available under the MIT license. See the LICENSE file for more info.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/itmsp
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'commander/import'
|
5
|
+
require 'itunes'
|
6
|
+
require 'itunes/transporter/commands'
|
7
|
+
|
8
|
+
Signal.trap("INT") {} # Suppress backtrace when exiting command
|
9
|
+
|
10
|
+
program :version, Itunes::VERSION
|
11
|
+
program :description, 'Generate iTunes Store Transporter package (.itmsp) from Achievements, Leaderboards, and/or In-App Purchases'
|
12
|
+
program :help, 'Author', 'Colin Humber'
|
13
|
+
|
14
|
+
program :help_formatter, :compact
|
15
|
+
|
16
|
+
default_command :help
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require './achievement'
|
2
|
+
|
3
|
+
def get_achievements
|
4
|
+
achievement1 = Achievement.new('achievement1', 'First Achievement', 10)
|
5
|
+
achievement1.add_locale(AchievementLocale.new('en', 'First Achievement', 'Unlock achievement 1', 'Unlock achievement 1', 'test.jpg'))
|
6
|
+
|
7
|
+
achievement2 = Achievement.new('achievement2', 'Second Achievement', 10)
|
8
|
+
achievement2.add_locale(AchievementLocale.new('en', 'Second Achievement', 'Unlock achievement 2', 'Unlock achievement 2', 'test.jpg'))
|
9
|
+
|
10
|
+
return [achievement1, achievement2]
|
11
|
+
end
|
data/example/config.yaml
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require './in_app_purchase'
|
2
|
+
|
3
|
+
def get_in_app_purchases
|
4
|
+
purchase1 = InAppPurchase.new('purchase1', 'First Purchase', CONSUMABLE, 'test.jpg')
|
5
|
+
purchase1.add_product(Product.new(true, 1))
|
6
|
+
purchase1.add_locale(ProductLocale.new('en', 'First Purchase', 'Some cools stuff.'))
|
7
|
+
|
8
|
+
return [purchase1]
|
9
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require './leaderboard'
|
2
|
+
|
3
|
+
def get_leaderboards
|
4
|
+
default = Leaderboard.new('leaderboard', 'Top Scores')
|
5
|
+
locale = LeaderboardLocale.new('en', 'Top Scores', INTEGER_COMMA_SEPARATOR, 'test.jpg')
|
6
|
+
locale.formatter_suffix = ' Points'
|
7
|
+
locale.formatter_suffix_singular = ' Points'
|
8
|
+
|
9
|
+
default.add_locale(locale)
|
10
|
+
|
11
|
+
return [default]
|
12
|
+
end
|
data/example/test.jpg
ADDED
Binary file
|
data/example/test.rb
ADDED
data/itmsp.gemspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'itunes_transporter_generator'
|
3
|
+
s.version = '0.2'
|
4
|
+
s.date = '2012-11-28'
|
5
|
+
s.summary = "iTunes Transporter App Store Package Generator"
|
6
|
+
s.description = "A command-line tool for generating and packaging app store assets for Game Center and In-App Purchases"
|
7
|
+
s.authors = ["Colin Humber"]
|
8
|
+
s.email = 'colinhumber@gmail.com'
|
9
|
+
s.homepage = 'https://github.com/colinhumber/itunes_transporter_generator'
|
10
|
+
|
11
|
+
s.add_dependency 'builder', '~>3.0.0'
|
12
|
+
|
13
|
+
s.files = Dir["./**/*"].reject { |file| file =~ /\.\/(bin|log|pkg|script|spec|test|vendor)/ }
|
14
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
15
|
+
s.require_paths = ['lib']
|
16
|
+
end
|
data/lib/itunes.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
module Itunes
|
2
|
+
module Transporter
|
3
|
+
class Achievement < Struct.new(:id, :name, :points, :hidden, :reusable, :should_remove, :locales)
|
4
|
+
end
|
5
|
+
|
6
|
+
class AchievementLocale < Struct.new(:name, :title, :before_earned_description, :after_earned_description, :after_earned_image, :should_remove)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'itunes/transporter/generator'
|
2
|
+
|
3
|
+
command :package do |c|
|
4
|
+
c.syntax = 'itmsp package [options]'
|
5
|
+
c.summary = ''
|
6
|
+
c.description = 'Generates iTunes Store Package metadata.xml from provided achievement, leaderboard, and/or in-app purchases provided'
|
7
|
+
c.example 'description', 'command example'
|
8
|
+
c.option '-i FILENAME', '--input FILENAME', String, 'YAML file containing app/team values, achievement, leaderboard, and/or in-app purchase descriptions'
|
9
|
+
|
10
|
+
c.action do |args, options|
|
11
|
+
Itunes::Transporter::Generator.new(options).generate_metadata
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
require 'builder'
|
2
|
+
require 'digest'
|
3
|
+
require 'itunes/transporter/helpers'
|
4
|
+
|
5
|
+
module Itunes
|
6
|
+
module Transporter
|
7
|
+
class Generator
|
8
|
+
include Itunes::Transporter::Helpers
|
9
|
+
|
10
|
+
attr_reader :files_to_process
|
11
|
+
attr_accessor :id_prefix
|
12
|
+
attr_accessor :provider
|
13
|
+
attr_accessor :team_id
|
14
|
+
attr_accessor :vendor_id
|
15
|
+
attr_accessor :achievements
|
16
|
+
attr_accessor :leaderboards
|
17
|
+
attr_accessor :purchases
|
18
|
+
|
19
|
+
def initialize(options)
|
20
|
+
@files_to_process = ['metadata.xml']
|
21
|
+
|
22
|
+
config_file = options.i || options.input
|
23
|
+
|
24
|
+
metadata = metadata_from_yaml(config_file)
|
25
|
+
|
26
|
+
@id_prefix = metadata[:id_prefix]
|
27
|
+
@provider = metadata[:provider]
|
28
|
+
@team_id = metadata[:team_id]
|
29
|
+
@vendor_id = metadata[:vendor_id]
|
30
|
+
@achievements = metadata[:achievements]
|
31
|
+
@leaderboards = metadata[:leaderboards]
|
32
|
+
@purchases = metadata[:purchases]
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_achievement_xml(doc, achievement, position)
|
36
|
+
doc.achievement('position' => position) do
|
37
|
+
doc.achievement_id(@id_prefix + achievement.id)
|
38
|
+
doc.reference_name(achievement.name)
|
39
|
+
doc.points(achievement.points)
|
40
|
+
doc.hidden(achievement.hidden)
|
41
|
+
doc.reusable(achievement.reusable)
|
42
|
+
doc.locales() do
|
43
|
+
achievement.locales.each do |locale|
|
44
|
+
doc.locale('name' => locale.name) do
|
45
|
+
doc.title(locale.title)
|
46
|
+
doc.before_earned_description(locale.before_earned_description)
|
47
|
+
doc.after_earned_description(locale.after_earned_description)
|
48
|
+
doc.after_earned_image() do
|
49
|
+
image_path = Dir.pwd + "/#{locale.after_earned_image}"
|
50
|
+
@files_to_process << image_path
|
51
|
+
|
52
|
+
doc.file_name(locale.after_earned_image)
|
53
|
+
doc.size(File.size?(image_path))
|
54
|
+
doc.checksum(Digest::MD5.file(image_path).hexdigest, 'type' => 'md5')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def create_leaderboard_xml(doc, leaderboard, position)
|
63
|
+
doc.leaderboard('default' => leaderboard.default, 'position' => position) do
|
64
|
+
doc.leaderboard_id(leaderboard.id)
|
65
|
+
doc.reference_name(leaderboard.name)
|
66
|
+
|
67
|
+
if leaderboard.aggregate_parent_leaderboard
|
68
|
+
doc.aggregate_parent_leaderboard(leaderboard.aggregate_parent_leaderboard)
|
69
|
+
end
|
70
|
+
|
71
|
+
doc.sort_ascending(leaderboard.sort_ascending)
|
72
|
+
|
73
|
+
if (doc.score_range_min != doc.score_range_max)
|
74
|
+
doc.score_range_min(leaderboard.score_range_min)
|
75
|
+
doc.score_range_max(leaderboard.score_range_max)
|
76
|
+
end
|
77
|
+
|
78
|
+
doc.locales() do
|
79
|
+
leaderboard.locales.each do |locale|
|
80
|
+
doc.locale('name' => locale.name) do
|
81
|
+
doc.title(locale.title)
|
82
|
+
|
83
|
+
if (locale.formatter_suffix)
|
84
|
+
doc.formatter_suffix(locale.formatter_suffix)
|
85
|
+
end
|
86
|
+
|
87
|
+
if (locale.formatter_suffix_singular)
|
88
|
+
doc.formatter_suffix_singular(locale.formatter_suffix_singular)
|
89
|
+
end
|
90
|
+
|
91
|
+
doc.formatter_type(locale.formatter_type)
|
92
|
+
doc.leaderboard_image() do
|
93
|
+
image_path = Dir.pwd + "/#{locale.leaderboard_image}"
|
94
|
+
@files_to_process << image_path
|
95
|
+
|
96
|
+
doc.file_name(locale.leaderboard_image)
|
97
|
+
doc.size(File.size?(image_path))
|
98
|
+
doc.checksum(Digest::MD5.file(image_path).hexdigest, 'type' => 'md5')
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def create_purchase_xml(doc, purchase)
|
107
|
+
doc.in_app_purchase() do
|
108
|
+
doc.product_id(@id_prefix + purchase.product_id)
|
109
|
+
doc.reference_name(purchase.reference_name)
|
110
|
+
doc.type(purchase.type)
|
111
|
+
|
112
|
+
doc.products() do
|
113
|
+
purchase.products.each do |product|
|
114
|
+
doc.product() do
|
115
|
+
doc.cleared_for_sale(product.cleared_for_sale)
|
116
|
+
doc.wholesale_price_tier(product.wholesale_price_tier)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
doc.locales() do
|
122
|
+
purchase.locales.each do |locale|
|
123
|
+
doc.locale('name' => locale.name) do
|
124
|
+
doc.title(locale.title)
|
125
|
+
doc.description(locale.description)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
doc.review_screenshot() do
|
131
|
+
image_path = Dir.pwd + "/#{purchase.review_screenshot_image}"
|
132
|
+
@files_to_process << image_path
|
133
|
+
|
134
|
+
doc.file_name(purchase.review_screenshot_image)
|
135
|
+
doc.size(File.size?(image_path))
|
136
|
+
doc.checksum(Digest::MD5.file(image_path).hexdigest, 'type' => 'md5')
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def generate_metadata
|
142
|
+
metadata_file = File.new(Dir.pwd + '/metadata.xml', 'w')
|
143
|
+
|
144
|
+
doc = Builder::XmlMarkup.new(:target => metadata_file, :indent => 2)
|
145
|
+
doc.instruct!(:xml, :version => '1.0', :encoding => 'UTF-8')
|
146
|
+
doc.package('xmlns' => 'http://apple.com/itunes/importer', 'version' => 'software5.0') do
|
147
|
+
doc.provider(@provider)
|
148
|
+
doc.team_id(@team_id)
|
149
|
+
doc.software() do
|
150
|
+
doc.vendor_id(@vendor_id)
|
151
|
+
doc.software_metadata() do
|
152
|
+
doc.game_center() do
|
153
|
+
if (@achievements.count > 0)
|
154
|
+
doc.achievements() do
|
155
|
+
@achievements.each_with_index do |val, index|
|
156
|
+
create_achievement_xml(doc, val, index + 1)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
if (@leaderboards.count > 0)
|
162
|
+
doc.leaderboards() do
|
163
|
+
@leaderboards.each_with_index do |val, index|
|
164
|
+
create_leaderboard_xml(doc, val, index + 1)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
if (@purchases.count > 0)
|
170
|
+
doc.in_app_purchases() do
|
171
|
+
@purchases.each do |val|
|
172
|
+
create_purchase_xml(doc, val)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
metadata_file.close()
|
182
|
+
|
183
|
+
generate_itmsp
|
184
|
+
end
|
185
|
+
|
186
|
+
def generate_itmsp
|
187
|
+
itmsp_dir = @vendor_id + '.itmsp'
|
188
|
+
Dir.mkdir(itmsp_dir)
|
189
|
+
|
190
|
+
@files_to_process.each do |file|
|
191
|
+
FileUtils.cp(file, itmsp_dir)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'itunes/transporter/achievement'
|
3
|
+
require 'itunes/transporter/leaderboard'
|
4
|
+
require 'itunes/transporter/in_app_purchase'
|
5
|
+
|
6
|
+
module Itunes
|
7
|
+
module Transporter
|
8
|
+
module Helpers
|
9
|
+
def metadata_from_yaml(yaml_file)
|
10
|
+
objs = YAML.load_file(Dir.pwd + "/#{yaml_file}")
|
11
|
+
|
12
|
+
metadata = {
|
13
|
+
:provider => objs['provider'],
|
14
|
+
:team_id => objs['team_id'],
|
15
|
+
:vendor_id => objs['vendor_id'],
|
16
|
+
:id_prefix => objs['id_prefix'] || '',
|
17
|
+
:achievements => parse_achievements(objs),
|
18
|
+
:leaderboards => parse_leaderboards(objs),
|
19
|
+
:purchases => parse_purchases(objs)
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse_achievements(objs)
|
24
|
+
achievements = []
|
25
|
+
|
26
|
+
objs['achievements'].each do |dict|
|
27
|
+
achievement = Achievement.new
|
28
|
+
achievement.id = dict['id']
|
29
|
+
achievement.name = dict['name']
|
30
|
+
achievement.points = dict['points']
|
31
|
+
achievement.hidden = dict.has_key?('hidden') ? dict['has_key'] : false
|
32
|
+
achievement.reusable = dict.has_key?('reusable') ? dict['reusable'] : false
|
33
|
+
achievement.should_remove = dict.has_key?('should_remove') ? dict['should_remove'] : false
|
34
|
+
achievement.locales = []
|
35
|
+
|
36
|
+
dict['locales'].each do |loc|
|
37
|
+
locale = AchievementLocale.new
|
38
|
+
locale.name = loc['name']
|
39
|
+
locale.title = loc['title']
|
40
|
+
locale.before_earned_description = loc['before_earned_description']
|
41
|
+
locale.after_earned_description = loc['after_earned_description']
|
42
|
+
locale.after_earned_image = loc['after_earned_image']
|
43
|
+
locale.should_remove = loc.has_key?('should_remove') ? loc['should_remove'] : false
|
44
|
+
|
45
|
+
achievement.locales << locale
|
46
|
+
end
|
47
|
+
|
48
|
+
achievements << achievement
|
49
|
+
end
|
50
|
+
|
51
|
+
achievements
|
52
|
+
end
|
53
|
+
|
54
|
+
def parse_leaderboards(objs)
|
55
|
+
leaderboards = []
|
56
|
+
|
57
|
+
objs['leaderboards'].each do |dict|
|
58
|
+
leaderboard = Leaderboard.new
|
59
|
+
leaderboard.default = dict['default']
|
60
|
+
leaderboard.name = dict['name']
|
61
|
+
leaderboard.aggregate_parent_leaderboard = dict['aggregate_parent_leaderboard']
|
62
|
+
leaderboard.sort_ascending = dict.has_key?('sort_ascending') ? dict['sort_ascending'] : false
|
63
|
+
leaderboard.score_range_min = dict['score_range_min']
|
64
|
+
leaderboard.score_range_max = dict['score_range_max']
|
65
|
+
leaderboard.locales = []
|
66
|
+
|
67
|
+
dict['locales'].each do |loc|
|
68
|
+
locale = LeaderboardLocale.new
|
69
|
+
locale.name = loc['name']
|
70
|
+
locale.title = loc['title']
|
71
|
+
locale.formatter_suffix = loc['formatter_suffix']
|
72
|
+
locale.formatter_suffix_singular = loc['formatter_suffix_singular']
|
73
|
+
locale.formatter_type = loc['formatter_type']
|
74
|
+
locale.leaderboard_image = loc['leaderboard_image']
|
75
|
+
locale.should_remove = loc.has_key?('should_remove') ? loc['should_remove'] : false
|
76
|
+
|
77
|
+
leaderboard.locales << locale
|
78
|
+
end
|
79
|
+
|
80
|
+
leaderboards << leaderboard
|
81
|
+
end
|
82
|
+
|
83
|
+
leaderboards
|
84
|
+
end
|
85
|
+
|
86
|
+
def parse_purchases(objs)
|
87
|
+
purchases = []
|
88
|
+
|
89
|
+
objs['purchases'].each do |dict|
|
90
|
+
purchase = InAppPurchase.new
|
91
|
+
purchase.product_id = dict['product_id']
|
92
|
+
purchase.reference_name = dict['reference_name']
|
93
|
+
purchase.type = dict['type']
|
94
|
+
purchase.duration = dict['duration']
|
95
|
+
purchase.free_trial_duration = dict['free_trial_duration']
|
96
|
+
purchase.bonus_duration = dict['bonus_duration']
|
97
|
+
purchase.review_screenshot_image = dict['review_screenshot_image']
|
98
|
+
purchase.review_notes = dict['review_notes']
|
99
|
+
purchase.should_remove = dict.has_key?('should_remove') ? dict['should_remove'] : false
|
100
|
+
purchase.locales = []
|
101
|
+
purchase.products = []
|
102
|
+
|
103
|
+
dict['products'].each do |p|
|
104
|
+
product = Product.new
|
105
|
+
product.cleared_for_sale = p['cleared_for_sale']
|
106
|
+
product.wholesale_price_tier = p['wholesale_price_tier']
|
107
|
+
product.should_remove = dict.has_key?('should_remove') ? dict['should_remove'] : false
|
108
|
+
product.intervals = []
|
109
|
+
|
110
|
+
intervals = p['intervals']
|
111
|
+
if intervals
|
112
|
+
p['intervals'].each do |i|
|
113
|
+
interval = Interval.new
|
114
|
+
interval.start_date = i['start_date']
|
115
|
+
interval.end_date = i['end_date']
|
116
|
+
interval.wholesale_price_tier = i['wholesale_price_tier']
|
117
|
+
|
118
|
+
product.intervals << interval
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
purchase.products << product
|
123
|
+
end
|
124
|
+
|
125
|
+
dict['locales'].each do |loc|
|
126
|
+
locale = PurchaseLocale.new
|
127
|
+
locale.name = loc['name']
|
128
|
+
locale.title = loc['title']
|
129
|
+
locale.description = loc['description']
|
130
|
+
locale.publication_name = loc['publication_name']
|
131
|
+
|
132
|
+
purchase.locales << locale
|
133
|
+
end
|
134
|
+
|
135
|
+
purchases << purchase
|
136
|
+
end
|
137
|
+
|
138
|
+
purchases
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Itunes
|
2
|
+
module Transporter
|
3
|
+
|
4
|
+
CONSUMABLE = 'consumable'
|
5
|
+
NON_CONSUMABLE = 'non-consumable'
|
6
|
+
SUBSCRIPTION = 'subscription'
|
7
|
+
FREE_SUBSCRIPTION = 'free-subscription'
|
8
|
+
|
9
|
+
DURATION_7_DAYS = '7 Days'
|
10
|
+
DURATION_1_MONTH = '1 Month'
|
11
|
+
DURATION_2_MONTHS = '2 Months'
|
12
|
+
DURATION_3_MONTHS = '3 Months'
|
13
|
+
DURATION_6_MONTH = '6 Months'
|
14
|
+
DURATION_1_YEAR = '1 Year'
|
15
|
+
|
16
|
+
class InAppPurchase < Struct.new(:product_id, :reference_name, :type, :duration, :free_trial_duration, :bonus_duration, :products, :locales, :review_screenshot_image, :review_notes, :should_remove)
|
17
|
+
end
|
18
|
+
|
19
|
+
class Product < Struct.new(:cleared_for_sale, :intervals, :should_remove, :wholesale_price_tier)
|
20
|
+
end
|
21
|
+
|
22
|
+
class Interval < Struct.new(:start_date, :end_date, :wholesale_price_tier)
|
23
|
+
end
|
24
|
+
|
25
|
+
class PurchaseLocale < Struct.new(:name, :title, :description, :publication_name)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'itunes/transporter/locale'
|
2
|
+
|
3
|
+
module Itunes
|
4
|
+
module Transporter
|
5
|
+
class Leaderboard < Struct.new(:default, :id, :name, :aggregate_parent_leaderboard, :sort_ascending, :score_range_min, :score_range_max, :locales)
|
6
|
+
end
|
7
|
+
|
8
|
+
class LeaderboardLocale < Struct.new(:name, :title, :formatter_suffix, :formatter_suffix_singular, :formatter_type, :leaderboard_image, :should_remove)
|
9
|
+
end
|
10
|
+
|
11
|
+
INTEGER_COMMA_SEPARATOR = 'INTEGER_COMMA_SEPARATOR'
|
12
|
+
INTEGER_DECIMAL_SEPARATOR = 'INTEGER_DECIMAL_SEPARATOR'
|
13
|
+
FIXED_POINT_1_PLACE_WITH_COMMA_SEPARATOR = 'FIXED_POINT_1_PLACE_WITH_COMMA_SEPARATOR'
|
14
|
+
FIXED_POINT_1_PLACE_WITH_DECIMAL_SEPARATOR = 'FIXED_POINT_1_PLACE_WITH_DECIMAL_SEPARATOR'
|
15
|
+
FIXED_POINT_2_PLACE_WITH_COMMA_SEPARATOR = 'FIXED_POINT_2_PLACE_WITH_COMMA_SEPARATOR'
|
16
|
+
FIXED_POINT_2_PLACE_WITH_DECIMAL_SEPARATOR = 'FIXED_POINT_2_PLACE_WITH_DECIMAL_SEPARATOR'
|
17
|
+
FIXED_POINT_3_PLACE_WITH_COMMA_SEPARATOR = 'FIXED_POINT_3_PLACE_WITH_COMMA_SEPARATOR'
|
18
|
+
FIXED_POINT_3_PLACE_WITH_DECIMAL_SEPARATOR = 'FIXED_POINT_3_PLACE_WITH_DECIMAL_SEPARATOR'
|
19
|
+
ELAPSED_TIME_TO_MINUTE = 'ELAPSED_TIME_TO_MINUTE'
|
20
|
+
ELAPSED_TIME_TO_SECOND = 'ELAPSED_TIME_TO_SECOND'
|
21
|
+
ELAPSED_TIME_TO_HUNDREDTH_OF_SECOND = 'ELAPSED_TIME_TO_HUNDREDTH_OF_SECOND'
|
22
|
+
MONEY_DOLLAR_WHOLE_WITH_COMMA_SEPARATOR = 'MONEY_DOLLAR_WHOLE_WITH_COMMA_SEPARATOR'
|
23
|
+
MONEY_EURO_WHOLE_WITH_DECIMAL_SEPARATOR = 'MONEY_EURO_WHOLE_WITH_DECIMAL_SEPARATOR'
|
24
|
+
MONEY_EURO_WHOLE_WITH_SPACE_SEPARATOR = 'MONEY_EURO_WHOLE_WITH_SPACE_SEPARATOR'
|
25
|
+
MONEY_POUND_WHOLE_WITH_COMMA_SEPARATOR = 'MONEY_POUND_WHOLE_WITH_COMMA_SEPARATOR'
|
26
|
+
MONEY_KR_WHOLE_WITH_COMMA_SEPARATOR = 'MONEY_KR_WHOLE_WITH_COMMA_SEPARATOR'
|
27
|
+
MONEY_FR_WHOLE_WITH_SPACE_SEPARATOR = 'MONEY_FR_WHOLE_WITH_SPACE_SEPARATOR'
|
28
|
+
MONEY_YEN_WHOLE_WITH_COMMA_SEPARATOR= 'MONEY_YEN_WHOLE_WITH_COMMA_SEPARATOR'
|
29
|
+
MONEY_DOLLAR_2DECIMALS_WITH_COMMA_SEPARATOR = 'MONEY_DOLLAR_2DECIMALS_WITH_COMMA_SEPARATOR'
|
30
|
+
MONEY_EURO_2DECIMALS_WITH_DECIMAL_SEPARATOR= 'MONEY_EURO_2DECIMALS_WITH_DECIMAL_SEPARATOR'
|
31
|
+
MONEY_EURO_2DECIMALSE_WITH_SPACE_SEPARATOR = 'MONEY_EURO_2DECIMALSE_WITH_SPACE_SEPARATOR'
|
32
|
+
MONEY_POUND_2DECIMALS_WITH_COMMA_SEPARATOR = 'MONEY_POUND_2DECIMALS_WITH_COMMA_SEPARATOR'
|
33
|
+
MONEY_KR_2DECIMALS_WITH_COMMA_SEPARATOR = 'MONEY_KR_2DECIMALS_WITH_COMMA_SEPARATOR'
|
34
|
+
MONEY_FR_2DECIMALS_WITH_SPACE_SEPARATOR = 'MONEY_FR_2DECIMALS_WITH_SPACE_SEPARATOR'
|
35
|
+
end
|
36
|
+
end
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: itunes_transporter_generator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.2'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Colin Humber
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-11-28 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: builder
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.0.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.0.0
|
30
|
+
description: A command-line tool for generating and packaging app store assets for
|
31
|
+
Game Center and In-App Purchases
|
32
|
+
email: colinhumber@gmail.com
|
33
|
+
executables:
|
34
|
+
- itmsp
|
35
|
+
extensions: []
|
36
|
+
extra_rdoc_files: []
|
37
|
+
files:
|
38
|
+
- ./example/achievements.rb
|
39
|
+
- ./example/config.yaml
|
40
|
+
- ./example/in_app_purchases.rb
|
41
|
+
- ./example/leaderboards.rb
|
42
|
+
- ./example/test.jpg
|
43
|
+
- ./example/test.rb
|
44
|
+
- ./itmsp.gemspec
|
45
|
+
- ./lib/itunes/transporter/achievement.rb
|
46
|
+
- ./lib/itunes/transporter/commands/package.rb
|
47
|
+
- ./lib/itunes/transporter/commands.rb
|
48
|
+
- ./lib/itunes/transporter/generator.rb
|
49
|
+
- ./lib/itunes/transporter/helpers.rb
|
50
|
+
- ./lib/itunes/transporter/in_app_purchase.rb
|
51
|
+
- ./lib/itunes/transporter/leaderboard.rb
|
52
|
+
- ./lib/itunes/transporter/locale.rb
|
53
|
+
- ./lib/itunes.rb
|
54
|
+
- ./Rakefile
|
55
|
+
- ./README.md
|
56
|
+
- bin/itmsp
|
57
|
+
homepage: https://github.com/colinhumber/itunes_transporter_generator
|
58
|
+
licenses: []
|
59
|
+
post_install_message:
|
60
|
+
rdoc_options: []
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ! '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
requirements: []
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 1.8.24
|
78
|
+
signing_key:
|
79
|
+
specification_version: 3
|
80
|
+
summary: iTunes Transporter App Store Package Generator
|
81
|
+
test_files: []
|