pindo 4.6.9

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.
Files changed (120) hide show
  1. checksums.yaml +7 -0
  2. data/bin/pindo +35 -0
  3. data/lib/pindo/base/aeshelper.rb +31 -0
  4. data/lib/pindo/base/deviceinfo.rb +90 -0
  5. data/lib/pindo/base/executable.rb +224 -0
  6. data/lib/pindo/base/githelper.rb +489 -0
  7. data/lib/pindo/base/hashhelper.rb +134 -0
  8. data/lib/pindo/base/plaininformative.rb +15 -0
  9. data/lib/pindo/base/plistbuddyexecutable.rb +121 -0
  10. data/lib/pindo/base/standarderror.rb +14 -0
  11. data/lib/pindo/client/applovinclient.rb +156 -0
  12. data/lib/pindo/client/aws3sclient.rb +183 -0
  13. data/lib/pindo/client/bossclient.rb +229 -0
  14. data/lib/pindo/client/bossconfigclient.rb +175 -0
  15. data/lib/pindo/client/giteeclient.rb +65 -0
  16. data/lib/pindo/client/httpclient.rb +40 -0
  17. data/lib/pindo/client/pgyerclient.rb +439 -0
  18. data/lib/pindo/client/tgateclient.rb +103 -0
  19. data/lib/pindo/command.rb +101 -0
  20. data/lib/pindo/config/pindoconfig.rb +439 -0
  21. data/lib/pindo/config/pindouserlocalconfig.rb +81 -0
  22. data/lib/pindo/deploy/Fastfile +233 -0
  23. data/lib/pindo/deploy/build.rb +167 -0
  24. data/lib/pindo/deploy/bundleid.rb +245 -0
  25. data/lib/pindo/deploy/cert.rb +508 -0
  26. data/lib/pindo/deploy/check.rb +79 -0
  27. data/lib/pindo/deploy/configproj.rb +89 -0
  28. data/lib/pindo/deploy/confusecode.rb +248 -0
  29. data/lib/pindo/deploy/confuseproj.rb +112 -0
  30. data/lib/pindo/deploy/fabric.rb +58 -0
  31. data/lib/pindo/deploy/getitcinfo.rb +248 -0
  32. data/lib/pindo/deploy/iap.rb +796 -0
  33. data/lib/pindo/deploy/initconfig.rb +128 -0
  34. data/lib/pindo/deploy/itcapp.rb +147 -0
  35. data/lib/pindo/deploy/itcinfo.rb +369 -0
  36. data/lib/pindo/deploy/pem.rb +55 -0
  37. data/lib/pindo/deploy/pullconfig.rb +46 -0
  38. data/lib/pindo/deploy/pushconfig.rb +83 -0
  39. data/lib/pindo/deploy/quswark.rb +146 -0
  40. data/lib/pindo/deploy/quswauth.rb +67 -0
  41. data/lib/pindo/deploy/reportbug.rb +135 -0
  42. data/lib/pindo/deploy/resign.rb +338 -0
  43. data/lib/pindo/deploy/tag.rb +108 -0
  44. data/lib/pindo/deploy/updateconfig.rb +129 -0
  45. data/lib/pindo/deploy/uploadipa.rb +63 -0
  46. data/lib/pindo/deploy.rb +44 -0
  47. data/lib/pindo/dev/autobuild.rb +204 -0
  48. data/lib/pindo/dev/autoresign.rb +186 -0
  49. data/lib/pindo/dev/confusecode.rb +127 -0
  50. data/lib/pindo/dev/confuseproj.rb +111 -0
  51. data/lib/pindo/dev/createbuild.rb +159 -0
  52. data/lib/pindo/dev/debug.rb +141 -0
  53. data/lib/pindo/dev/pub.rb +171 -0
  54. data/lib/pindo/dev/renewcert.rb +142 -0
  55. data/lib/pindo/dev.rb +23 -0
  56. data/lib/pindo/env/dreamstudio.rb +53 -0
  57. data/lib/pindo/env/flutter.rb +59 -0
  58. data/lib/pindo/env/flutter.sh +116 -0
  59. data/lib/pindo/env/quarkenv.rb +58 -0
  60. data/lib/pindo/env/swarkenv.rb +62 -0
  61. data/lib/pindo/env/workhard.rb +56 -0
  62. data/lib/pindo/env.rb +17 -0
  63. data/lib/pindo/ipa/autoresign.rb +188 -0
  64. data/lib/pindo/ipa/import.rb +261 -0
  65. data/lib/pindo/ipa/output.rb +396 -0
  66. data/lib/pindo/ipa.rb +22 -0
  67. data/lib/pindo/lib/forcepush.rb +159 -0
  68. data/lib/pindo/lib/lint.rb +115 -0
  69. data/lib/pindo/lib/push.rb +143 -0
  70. data/lib/pindo/lib/update.rb +75 -0
  71. data/lib/pindo/lib.rb +18 -0
  72. data/lib/pindo/module/appselect.rb +200 -0
  73. data/lib/pindo/module/appstore_in_app_purchase.rb +1803 -0
  74. data/lib/pindo/module/appstore_metadata_connect_api_helper.rb +12 -0
  75. data/lib/pindo/module/appstore_metadata_fastlane_helper.rb +922 -0
  76. data/lib/pindo/module/buildconfighelper.rb +13 -0
  77. data/lib/pindo/module/buildhelper.rb +76 -0
  78. data/lib/pindo/module/commonconfuseproj.rb +271 -0
  79. data/lib/pindo/module/config_project.sh +143 -0
  80. data/lib/pindo/module/configprojhelper.rb +631 -0
  81. data/lib/pindo/module/iap_tier.json +813 -0
  82. data/lib/pindo/module/icon_contents.json +116 -0
  83. data/lib/pindo/module/imessage_icon.json +91 -0
  84. data/lib/pindo/module/imgset_contents.json +21 -0
  85. data/lib/pindo/module/launchimg_contents.json +21 -0
  86. data/lib/pindo/module/pemcreate.rb +63 -0
  87. data/lib/pindo/module/pgyerhelper.rb +339 -0
  88. data/lib/pindo/module/xcodebuildpre.rb +258 -0
  89. data/lib/pindo/module/xcodehelper.rb +521 -0
  90. data/lib/pindo/options/appconfigoptions.rb +23 -0
  91. data/lib/pindo/options/deployoptions.rb +363 -0
  92. data/lib/pindo/pgyer/apptest.rb +104 -0
  93. data/lib/pindo/pgyer/comment.rb +95 -0
  94. data/lib/pindo/pgyer/download.rb +105 -0
  95. data/lib/pindo/pgyer/login.rb +61 -0
  96. data/lib/pindo/pgyer/upload.rb +234 -0
  97. data/lib/pindo/pgyer.rb +17 -0
  98. data/lib/pindo/repo/clone.rb +78 -0
  99. data/lib/pindo/repo/create.rb +55 -0
  100. data/lib/pindo/repo/login.rb +64 -0
  101. data/lib/pindo/repo/search.rb +60 -0
  102. data/lib/pindo/repo.rb +16 -0
  103. data/lib/pindo/setup.rb +60 -0
  104. data/lib/pindo/upgrade.rb +56 -0
  105. data/lib/pindo/utils/applovin.rb +213 -0
  106. data/lib/pindo/utils/boss.rb +209 -0
  107. data/lib/pindo/utils/clearcert.rb +60 -0
  108. data/lib/pindo/utils/device.rb +279 -0
  109. data/lib/pindo/utils/icon.rb +91 -0
  110. data/lib/pindo/utils/icon.sh +133 -0
  111. data/lib/pindo/utils/podindex.rb +56 -0
  112. data/lib/pindo/utils/podindex.sh +30 -0
  113. data/lib/pindo/utils/renewproj.rb +146 -0
  114. data/lib/pindo/utils/tgate.rb +159 -0
  115. data/lib/pindo/utils/xcassets.rb +46 -0
  116. data/lib/pindo/utils/xcassets.sh +88 -0
  117. data/lib/pindo/utils.rb +29 -0
  118. data/lib/pindo/version.rb +110 -0
  119. data/lib/pindo.rb +34 -0
  120. metadata +364 -0
@@ -0,0 +1,796 @@
1
+ require 'app_store_dev_api'
2
+ require 'json'
3
+ require 'faraday'
4
+
5
+
6
+ module Pindo
7
+ class Command
8
+ class Deploy < Command
9
+ class Iap < Deploy
10
+
11
+ include Command::DeployOptions
12
+
13
+ include AppStoreInAppPurchase
14
+
15
+ self.summary = '创建、修改AppStore中的Iap购买项,生成boss的iap购买项'
16
+
17
+ self.description = <<-DESC
18
+ 创建、修改AppStore中的Iap购买项,生成boss的iap购买项
19
+ DESC
20
+
21
+ self.arguments = [
22
+ CLAide::Argument.new('path/to/config.json', true),
23
+ ]
24
+
25
+ def self.options
26
+ [
27
+
28
+ ['--check', '检查iap信息'],
29
+ ['--download', '从appstore下载app的iap信息,用法: pindo deploy iap --download'],
30
+ ['--boss', '生成boss的iap信息, 用法: pindo deploy iap --boss'],
31
+
32
+ ].concat(super)
33
+ end
34
+
35
+ def validate!
36
+ super
37
+
38
+ pindo_dir = File::expand_path(pindo_single_config.pindo_dir)
39
+ config_repo_dir = File.join(pindo_dir, @bundle_id)
40
+ @iapconfig_dir =File.join(config_repo_dir, "fastlane/iap")
41
+
42
+ puts "iap dir: " + @iapconfig_dir
43
+ puts
44
+
45
+ end
46
+
47
+ def initialize(argv)
48
+
49
+ @check_flag = argv.flag?('check', false)
50
+ @download_flag = argv.flag?('download', false)
51
+ @boss_flag = argv.flag?('boss', false)
52
+
53
+ super
54
+ end
55
+
56
+ def run
57
+
58
+
59
+ if @download_flag
60
+
61
+ login_api_key(apple_id:@apple_id)
62
+ Spaceship::Tunes.login(@apple_id)
63
+
64
+ appstore_iap_file = File.join(@iapconfig_dir, "iap_appstore_download.json")
65
+ download_appstore_iap(appstore_iap_file:appstore_iap_file)
66
+ elsif @boss_flag
67
+
68
+ # puts "default boss vip auto =========\n\n\n"
69
+ # appstore_iap_file = File.join(@iapconfig_dir, "iap_appstore.json")
70
+ # produce_boss_iap(appstore_iap_file:appstore_iap_file, vip_mode:"autovip")
71
+
72
+ puts "one === first auto vip for boss =========\n\n\n"
73
+ appstore_iap_file = File.join(@iapconfig_dir, "one/iap_appstore.json")
74
+ produce_boss_iap(appstore_iap_file:appstore_iap_file, vip_mode:"autovip")
75
+
76
+ puts "two === second fancy vip for boss =========\n\n\n"
77
+ appstore_iap_file = File.join(@iapconfig_dir, "two/iap_appstore.json")
78
+ produce_boss_iap(appstore_iap_file:appstore_iap_file, vip_mode:"fancyvip")
79
+
80
+ puts "three === third funny vip for boss =========\n\n\n"
81
+ appstore_iap_file = File.join(@iapconfig_dir, "three/iap_appstore.json")
82
+ produce_boss_iap(appstore_iap_file:appstore_iap_file, vip_mode:"funnyvip")
83
+
84
+ else
85
+
86
+ login_api_key(apple_id:@apple_id)
87
+ Spaceship::Tunes.login(@apple_id)
88
+
89
+ create_iap_tester()
90
+ appstore_iap_file = File.join(@iapconfig_dir, "iap_appstore.json")
91
+ create_appstore_iap(appstore_iap_file:appstore_iap_file)
92
+ end
93
+ end
94
+
95
+ def download_appstore_iap(appstore_iap_file:nil)
96
+ puts "正在下载appstore iap 信息 ..."
97
+
98
+ app_list_response = @app_store_connect.list_apps(filter: {
99
+ bundle_id: @bundle_id
100
+ })
101
+
102
+ current_app = nil
103
+ app_list_response[:data].each do |app_info|
104
+ if app_info[:attributes][:bundle_id].to_s.eql?(@bundle_id)
105
+ current_app = app_info
106
+ end
107
+ end
108
+
109
+ app_id = current_app[:id]
110
+
111
+ puts " "
112
+ puts "app_id : #{app_id} +++++++++"
113
+
114
+ in_app_purchase_json = {}
115
+
116
+ puts "正在下载消耗型购买项..."
117
+ in_app_purchase_type = "CONSUMABLE"
118
+ in_app_purchase_json["consumable"] = get_consumable_iap_items(appstore_client:@app_store_connect, app_id:app_id, in_app_purchase_type:in_app_purchase_type) || []
119
+
120
+ puts "正在下载非消耗型购买项..."
121
+ in_app_purchase_type = "NON_CONSUMABLE"
122
+ in_app_purchase_json["non_consumable"] = get_consumable_iap_items(appstore_client:@app_store_connect, app_id:app_id, in_app_purchase_type:in_app_purchase_type) || []
123
+
124
+ puts "正在下载非自动订阅购买项..."
125
+ in_app_purchase_type = "NON_RENEWING_SUBSCRIPTION"
126
+ in_app_purchase_json["non_renew"] = get_consumable_iap_items(appstore_client:@app_store_connect, app_id:app_id, in_app_purchase_type:in_app_purchase_type) || []
127
+
128
+ puts "正在下载自动订阅购买项..."
129
+ subscription_group_items = get_subscriptions_groups(appstore_client:@app_store_connect, app_id:app_id)
130
+ in_app_purchase_json["auto_renew"] = subscription_group_items
131
+
132
+ puts JSON.pretty_generate(in_app_purchase_json)
133
+ File.open(appstore_iap_file, "w") do |f|
134
+ f.write(JSON.pretty_generate(in_app_purchase_json))
135
+ end
136
+
137
+ pindo_dir = File::expand_path(pindo_single_config.pindo_dir)
138
+ config_repo_dir = File.join(pindo_dir, @deploy_identifier)
139
+ git_addpush_repo(path:config_repo_dir, message:"download app iap", commit_file_params:["fastlane/iap/iap_appstore_download.json"])
140
+
141
+ update_app_share_secrets(app_id:app_id, need_create:false)
142
+ end
143
+
144
+ def produce_boss_iap(appstore_iap_file:nil, vip_mode:"autovip")
145
+
146
+ if !File.exist?(appstore_iap_file)
147
+
148
+ return
149
+ end
150
+
151
+ appstore_iap_file =File::expand_path(appstore_iap_file)
152
+ appstore_iap_json = JSON.parse(File.read(appstore_iap_file))
153
+
154
+ boss_iap_file =File.join(File::dirname(appstore_iap_file), "iap_boss.json")
155
+
156
+
157
+ if !appstore_iap_json.nil? && !appstore_iap_json["auto_renew"].nil?
158
+
159
+ appstore_iap_json["auto_renew"].each do |vip_group_item|
160
+ if !vip_group_item.nil?
161
+
162
+ appstore_iap_items = vip_group_item["items"] || vip_group_item["subscription_items"]
163
+
164
+ boss_iap_json = {}
165
+ boss_iap_json["client_vip"] = {}
166
+ boss_iap_json["offer_map"] = {}
167
+ boss_iap_json["client_vip"]["style"] = "tribble_style"
168
+ boss_iap_json["client_vip"]["best_deal"] = "offer.id." + vip_mode + "0"
169
+ boss_iap_json["client_vip"]["offer"] = []
170
+
171
+ if !appstore_iap_items.nil? && appstore_iap_items.size > 0
172
+
173
+ appstore_iap_items.each do |appstore_iap_item|
174
+
175
+ boss_iap_item = {}
176
+ boss_iap_item["offer_id"] = "offer.id." + vip_mode
177
+ boss_iap_item["kind"] = "vip"
178
+ boss_iap_item["way"] = "iap"
179
+ boss_iap_item["type"] = "auto_renewable_subscription"
180
+ boss_iap_item["subscription"] = {}
181
+
182
+ if !appstore_iap_item["subscription_free_trial"].nil? && appstore_iap_item["subscription_free_trial"].eql?("3d")
183
+ boss_iap_item["free_days"] = 3
184
+ end
185
+ boss_iap_item["iap"] = {}
186
+ boss_iap_item["iap"]["price"] = appstore_iap_item["price"]
187
+
188
+ if appstore_iap_item["subscription_duration"].include?("w")
189
+ boss_iap_item["offer_id"] = boss_iap_item["offer_id"] + "0"
190
+ boss_iap_item["subscription"]["period_type"] = "week"
191
+ boss_iap_item["subscription"]["period"] = appstore_iap_item["subscription_duration"].gsub('w', '').to_i
192
+ elsif appstore_iap_item["subscription_duration"].include?("m")
193
+ boss_iap_item["offer_id"] = boss_iap_item["offer_id"] + "1"
194
+ boss_iap_item["subscription"]["period_type"] = "month"
195
+ boss_iap_item["subscription"]["period"] = appstore_iap_item["subscription_duration"].gsub('m', '').to_i
196
+ boss_iap_item["iap"]["discount"] = 15
197
+ elsif appstore_iap_item["subscription_duration"].include?("y")
198
+ boss_iap_item["offer_id"] = boss_iap_item["offer_id"] + "2"
199
+ boss_iap_item["subscription"]["period_type"] = "year"
200
+ boss_iap_item["subscription"]["period"] = appstore_iap_item["subscription_duration"].gsub('y', '').to_i
201
+ boss_iap_item["iap"]["discount"] = 50
202
+ end
203
+
204
+ if !boss_iap_item["iap"]["price"].to_s.include?("$")
205
+ boss_iap_item["iap"]["price"] = "$" + boss_iap_item["iap"]["price"].to_s
206
+ end
207
+
208
+ boss_iap_item["iap"]["product_id"] = appstore_iap_item["product_id"]
209
+ if boss_iap_item["iap"]["product_id"].include?("PRODUCT_BUNDLE_IDENTIFIER")
210
+ temp_product_id = boss_iap_item["iap"]["product_id"]
211
+ temp_product_id = temp_product_id.gsub(/PRODUCT_BUNDLE_IDENTIFIER/, @bundle_id);
212
+ boss_iap_item["iap"]["product_id"] = temp_product_id
213
+ end
214
+
215
+ boss_iap_json["offer_map"][boss_iap_item["offer_id"]] = boss_iap_item
216
+ boss_iap_json["client_vip"]["offer"] << boss_iap_item["offer_id"]
217
+
218
+ end
219
+
220
+ end
221
+
222
+ puts JSON.pretty_generate(boss_iap_json)
223
+ puts "\n\n\n"
224
+ File.open(boss_iap_file, "w") do |f|
225
+ f.write(JSON.pretty_generate(boss_iap_json))
226
+ end
227
+
228
+
229
+ end
230
+ end
231
+ end
232
+
233
+ end
234
+
235
+
236
+ def update_app_share_secrets(app_id:nil, need_create:true)
237
+
238
+ app_secret_id = get_app_share_secrets(app_id:app_id, need_create:need_create)
239
+ if !app_secret_id.nil?
240
+ puts
241
+ puts "App Shared Secret: #{app_secret_id}"
242
+ puts
243
+ pindo_dir = File::expand_path(pindo_single_config.pindo_dir)
244
+ config_repo_dir = File.join(pindo_dir, @deploy_identifier)
245
+
246
+ app_origin_config = File.join(config_repo_dir, "config.json")
247
+ config_json = JSON.parse(File.read(app_origin_config))
248
+ config_json['app_info']['appstore_iap_secret'] = app_secret_id
249
+ File.open(app_origin_config, "w") do |file|
250
+ file.write(JSON.pretty_generate(config_json))
251
+ file.close
252
+ end
253
+
254
+ config_json = JSON.parse(File.read(@args_appconfig_fullname))
255
+ config_json['app_info']['appstore_iap_secret'] = app_secret_id
256
+ File.open(@args_appconfig_fullname, "w") do |file|
257
+ file.write(JSON.pretty_generate(config_json))
258
+ file.close
259
+ end
260
+
261
+ git_addpush_repo(path:config_repo_dir, message:"update app shared secret", commit_file_params:["config.json"])
262
+
263
+ end
264
+
265
+
266
+ end
267
+
268
+ def create_appstore_iap(appstore_iap_file:nil)
269
+
270
+ temp_iap_json = JSON.parse(File.read(appstore_iap_file))
271
+
272
+ if @check_flag
273
+ check_purchase_items_right(in_app_purchase_json:temp_iap_json, config_json:@config_json)
274
+ end
275
+
276
+ in_app_purchase_json = process_purchase_items(in_app_purchase_json:temp_iap_json, iap_tester:@iaptester)
277
+
278
+
279
+ app_list_response = @app_store_connect.list_apps(filter: {
280
+ bundle_id: @bundle_id
281
+ })
282
+
283
+
284
+ # puts app_list_response
285
+ current_app = nil
286
+
287
+ app_list_response[:data].each do |app_info|
288
+ if app_info[:attributes][:bundle_id].to_s.eql?(@bundle_id)
289
+ current_app = app_info
290
+ end
291
+ end
292
+
293
+ app_id = current_app[:id]
294
+
295
+ puts " "
296
+ puts "app_id : #{app_id} +++++++++"
297
+
298
+ puts " "
299
+ puts "创建消耗型购买项============="
300
+ create_consumable_iap_items(appstore_client:@app_store_connect, app_id:app_id, in_app_purchase_items:in_app_purchase_json["consumable"])
301
+
302
+ puts " "
303
+ puts "创建非消耗型购买项============="
304
+ create_consumable_iap_items(appstore_client:@app_store_connect, app_id:app_id, in_app_purchase_items:in_app_purchase_json["non_consumable"])
305
+
306
+ puts " "
307
+ puts "创建非续期订阅购买项============="
308
+ create_consumable_iap_items(appstore_client:@app_store_connect, app_id:app_id, in_app_purchase_items:in_app_purchase_json["non_renew"])
309
+
310
+ puts " "
311
+ puts "创建自动订阅购买项============="
312
+ create_subscriptions_groups(appstore_client:@app_store_connect, app_id:app_id, subscription_group_items:in_app_purchase_json["auto_renew"])
313
+
314
+ update_app_share_secrets(app_id:app_id)
315
+ end
316
+
317
+ def login_api_key (apple_id:nil)
318
+
319
+ load_apikey_config = false
320
+ api_key_json = nil
321
+ pindo_dir = File::expand_path(pindo_single_config.pindo_dir)
322
+ api_key_file = File.join(pindo_dir, "api_key.json")
323
+
324
+ if File.exist?(api_key_file)
325
+ api_key_json = JSON.parse(File.read(api_key_file))
326
+ if !api_key_json.nil? && !api_key_json[apple_id].nil? && !api_key_json[apple_id]["issuer_id"].nil?
327
+ issuer_id = api_key_json[apple_id]["issuer_id"]
328
+ key_id = api_key_json[apple_id]["key_id"]
329
+ private_key = api_key_json[apple_id]["private_key"]
330
+ load_apikey_config = true
331
+ end
332
+ end
333
+
334
+ if !load_apikey_config
335
+ issuer_id = ask('请输入 issuer_id: ')
336
+ key_id = ask('请输入 key_id: ')
337
+ private_key_file = ask('请输入private key的文件路径: ')
338
+ private_key = File.read(File.new(private_key_file))
339
+
340
+ api_key_json = api_key_json || {}
341
+ api_key_json[apple_id] = api_key_json[apple_id] || {}
342
+ api_key_json[apple_id]["issuer_id"] = issuer_id
343
+ api_key_json[apple_id]["key_id"] = key_id
344
+ api_key_json[apple_id]["private_key"] = private_key
345
+
346
+ answer = agree("请确认上面信息是否正确(Y/n):")
347
+ unless answer
348
+ raise Informative, "重新输入 !!!"
349
+ end
350
+
351
+ File.open(api_key_file, "w") do |f|
352
+ f.write(JSON.pretty_generate(api_key_json))
353
+ end
354
+ load_apikey_config = true
355
+
356
+
357
+ end
358
+
359
+ if load_apikey_config
360
+
361
+ puts "issuer_id : #{issuer_id}"
362
+ puts "key_id : #{key_id}"
363
+ puts "private_key : #{private_key}"
364
+
365
+ AppStoreDevApi.config = {
366
+ issuer_id: issuer_id,
367
+ key_id: key_id,
368
+ private_key: private_key
369
+ }
370
+ @app_store_connect = AppStoreDevApi::Client.new
371
+ end
372
+ end
373
+
374
+
375
+ def check_purchase_items_right(in_app_purchase_json:nil, config_json:nil)
376
+
377
+ puts
378
+ puts "检查iap 配置"
379
+ puts
380
+ app_version = @config_json["app_info"]["app_version"]
381
+ bundle_id = @bundle_id
382
+ platform = @config_json["app_setting"]["kGUKeyAppPlatform"] || @config_json["app_setting"]["app_platform"]
383
+ app_type = @config_json["project_info"]["app_type"]
384
+
385
+ boss_client = BossClient.new(app_type:app_type, is_dev:false)
386
+ data_json = Pindo::Command::Utils::Boss::get_config(boss_client:boss_client, app_version:app_version, bundle_id:bundle_id, platform:platform)
387
+
388
+ # if !data_json["data"].nil?
389
+ # puts JSON.pretty_generate(data_json["data"])
390
+ # else
391
+ # puts JSON.pretty_generate(data_json)
392
+ # end
393
+
394
+ gate_vip_items = get_gate_vip_items(app_type:app_type, boss_config:data_json["data"])
395
+
396
+ if !gate_vip_items.nil? && gate_vip_items.size > 0
397
+ itc_vip_items = []
398
+ in_app_purchase_json["auto_renew"].each do |group_item|
399
+ items = group_item["subscription_items"] || []
400
+ itc_vip_items += items
401
+ items = group_item["items"] || []
402
+ itc_vip_items += items
403
+ end
404
+
405
+
406
+ gate_vip_items.each do |offer_id, boss_iap_item|
407
+ boss_product_id = boss_iap_item["iap"]["product_id"]
408
+ boss_price = boss_iap_item["iap"]["price"]
409
+ free_days = boss_iap_item["free_days"]
410
+
411
+ itc_vip_item = itc_vip_items.find { |item| item["product_id"].eql?(boss_product_id)}
412
+ if !itc_vip_item.nil?
413
+ puts "product id: #{boss_product_id} 与boss一致!"
414
+
415
+ if boss_price.eql?("$"+itc_vip_item["price"].to_s)
416
+ puts " 价格一致! 价格:#{boss_price}"
417
+ else
418
+ local_price = itc_vip_item["price"].to_s
419
+ raise Informative, " 价格不一致 Boss:#{boss_price}, Local:$#{local_price}"
420
+ end
421
+
422
+ if !itc_vip_item["subscription_free_trial"].nil? && !free_days.nil? && itc_vip_item["subscription_free_trial"].to_s.eql?(free_days.to_s + "d")
423
+ puts " 有三天试用, 一致!"
424
+ elsif itc_vip_item["subscription_free_trial"].nil? && free_days.nil?
425
+ puts " 无三天试用, 一致!"
426
+ else
427
+ raise Informative, " 三天试用不一致!"
428
+ end
429
+
430
+
431
+ else
432
+ raise Informative, "product id: #{boss_product_id} 与boss不匹配!"
433
+ end
434
+ end
435
+ end
436
+
437
+ offer_vip_items = get_gate_vip_items(app_type:app_type, boss_config:data_json["data"])
438
+ end
439
+
440
+
441
+
442
+
443
+ def get_gate_vip_items(app_type:nil, boss_config:nil)
444
+ vip_items = nil
445
+
446
+ offer_ids = []
447
+ if !boss_config.nil? && !boss_config["content"].nil? && !boss_config["content"]["client"].nil? && !boss_config["content"]["client"]["vip"].nil? && !boss_config["content"]["client"]["vip"]["style_list"].nil?
448
+ style_list = boss_config["content"]["client"]["vip"]["style_list"] || {}
449
+ style_item = style_list.find { |item| item["style"].eql?("tribble_style")}
450
+ offer_ids = style_item["offer"] || []
451
+ end
452
+
453
+ if !boss_config.nil? && !boss_config["content"].nil? && !boss_config["content"]["common"].nil? && !boss_config["content"]["common"].nil? && !boss_config["content"]["common"]["offer_map"].nil?
454
+ boss_vip_items = boss_config["content"]["common"]["offer_map"]
455
+ vip_items = vip_items || {}
456
+ offer_ids.each do |offer_id|
457
+ if !boss_vip_items[offer_id].nil?
458
+ vip_items[offer_id] = boss_vip_items[offer_id]
459
+ end
460
+ end
461
+ end
462
+
463
+ return vip_items
464
+ end
465
+
466
+ def process_purchase_items(in_app_purchase_json:nil, iap_tester:nil)
467
+ new_in_app_purchase_json = {}
468
+ new_in_app_purchase_json["iaptester"] = @iaptester
469
+ new_in_app_purchase_json["bundle_id"] = @bundle_id
470
+
471
+ new_in_app_purchase_json["review_note"] = "username: " + iap_tester["email"] + " password: " + iap_tester["password"]
472
+
473
+ puts
474
+ puts
475
+ puts new_in_app_purchase_json["review_note"]
476
+
477
+ in_app_purchase_type = "CONSUMABLE"
478
+ items = process_consumable_iap_items(in_app_purchase_type:in_app_purchase_type, in_app_purchase_items:in_app_purchase_json["consumable"], iap_tester:iap_tester)
479
+ new_in_app_purchase_json["consumable"] = items
480
+
481
+ in_app_purchase_type = "NON_CONSUMABLE"
482
+ items = process_consumable_iap_items(in_app_purchase_type:in_app_purchase_type, in_app_purchase_items:in_app_purchase_json["non_consumable"], iap_tester:iap_tester)
483
+ new_in_app_purchase_json["non_consumable"] = items
484
+
485
+ in_app_purchase_type = "NON_RENEWING_SUBSCRIPTION"
486
+ items = process_consumable_iap_items(in_app_purchase_type:in_app_purchase_type, in_app_purchase_items:in_app_purchase_json["non_renew"], iap_tester:iap_tester)
487
+ new_in_app_purchase_json["non_renew"] = items
488
+
489
+ in_app_purchase_type = "Subscriptions"
490
+ new_subscrptions_json = process_subscriptions_json(subscription_json:in_app_purchase_json["auto_renew"], iap_tester:iap_tester)
491
+ new_in_app_purchase_json["auto_renew"] = new_subscrptions_json
492
+
493
+ File.open(File.join(@iapconfig_dir, "iap_log.json"), "w") do |f|
494
+ f.write(JSON.pretty_generate(new_in_app_purchase_json))
495
+ end
496
+
497
+ return new_in_app_purchase_json;
498
+
499
+ end
500
+
501
+ def process_consumable_iap_items(in_app_purchase_type:nil, in_app_purchase_items:nil, iap_tester:nil)
502
+
503
+ tier_json = get_price_tier_dict
504
+ iap_consumable_tier_dict = tier_json["consumable_price_tier"]
505
+
506
+ in_app_purchase_items = in_app_purchase_items || []
507
+ new_in_app_purchase_items = []
508
+ in_app_purchase_items.each do |purchase_item|
509
+ new_iap_items = {}
510
+ new_iap_items["reference_name"] = purchase_item["reference_name"].to_s.strip || purchase_item["product_name"].to_s.strip
511
+ if !purchase_item["reference_name"].nil? && purchase_item["reference_name"].length > 64
512
+ puts purchase_item["reference_name"]
513
+ puts "length: #{purchase_item["reference_name"].size}"
514
+ raise Informative, "Reference Name 超出限定的64个字符 !!!"
515
+ end
516
+
517
+ temp_product_id = purchase_item["product_id"].to_s.strip
518
+ new_iap_items["product_id"] = temp_product_id
519
+ if new_iap_items["product_id"].include?("PRODUCT_BUNDLE_IDENTIFIER")
520
+ temp_product_id = new_iap_items["product_id"]
521
+ temp_product_id = temp_product_id.gsub(/PRODUCT_BUNDLE_IDENTIFIER/, @bundle_id);
522
+ new_iap_items["product_id"] = temp_product_id
523
+ end
524
+ if !new_iap_items["product_id"].nil? && new_iap_items["product_id"].length > 100
525
+ puts temp_product_id
526
+ raise Informative, "product_id 超出限定的100个字符 !!!"
527
+ end
528
+
529
+ new_iap_items["in_app_purchase_type"] = in_app_purchase_type
530
+ new_iap_items["available_in_all_territories"] = true
531
+ new_iap_items["price"] = purchase_item["price"].to_s.strip
532
+ new_iap_items["price_tier"] = iap_consumable_tier_dict[new_iap_items["price"]]
533
+
534
+ if new_iap_items["price_tier"].nil?
535
+ raise "购买项价格错误 " + new_iap_items["product_id"] + " 价格 :" + purchase_item["price"]
536
+ end
537
+
538
+ new_iap_items["localization"] = {}
539
+ new_iap_items["localization"]["en-US"] = {}
540
+ new_iap_items["localization"]["en-US"]["locale"] = "en-US"
541
+ new_iap_items["localization"]["en-US"]["display_name"] = purchase_item["display_name"].to_s.strip
542
+
543
+ if !new_iap_items["localization"]["en-US"]["display_name"].nil? && new_iap_items["localization"]["en-US"]["display_name"].length > 30
544
+ puts temp_product_id
545
+ raise Informative, "Display Name 超出限定的30个字符 !!!"
546
+ end
547
+
548
+ new_iap_items["localization"]["en-US"]["description"] = purchase_item["description"].to_s.strip
549
+ if !new_iap_items["localization"]["en-US"]["description"].nil? && new_iap_items["localization"]["en-US"]["description"].length > 45
550
+ puts temp_product_id
551
+ raise Informative, "description 超出限定的45个字符 !!!"
552
+ end
553
+
554
+ new_iap_items["review_note"] = "username: " + iap_tester["email"] + " password: " + iap_tester["password"]
555
+ if !purchase_item["review_screenshot"].nil?
556
+ new_iap_items["review_screenshot"] = File.basename(purchase_item["review_screenshot"])
557
+ new_iap_items["review_screenshot_fullpath"] = File.join(@iapconfig_dir, new_iap_items["review_screenshot"])
558
+ else
559
+ new_iap_items["review_screenshot_fullpath"] = nil
560
+ end
561
+
562
+ new_in_app_purchase_items << new_iap_items
563
+ end
564
+
565
+ return new_in_app_purchase_items
566
+ end
567
+
568
+
569
+ def process_subscriptions_json(subscription_json:nil, iap_tester:nil)
570
+
571
+ tier_json = get_price_tier_dict
572
+ subscription_json = subscription_json || []
573
+ new_subscription_json = []
574
+ subscription_json.each do |subscription_group_item|
575
+ new_subscription_group_item = {}
576
+ new_subscription_group_item["group_reference_name"] = subscription_group_item["group_reference_name"].to_s.strip
577
+ new_subscription_group_item["localization"] = {}
578
+ new_subscription_group_item["localization"]["en-US"] = {}
579
+ new_subscription_group_item["localization"]["en-US"]["locale"] = "en-US"
580
+ new_subscription_group_item["localization"]["en-US"]["group_display_name"] = subscription_group_item["group_display_name"].to_s.strip
581
+
582
+ if !new_subscription_group_item["localization"]["en-US"]["group_display_name"].nil? && new_subscription_group_item["localization"]["en-US"]["group_display_name"].length > 45
583
+ puts new_subscription_group_item["localization"]["en-US"]["group_display_name"]
584
+ raise Informative, "group_display_name 超出限定的75个字符 !!!"
585
+ end
586
+ new_subscription_group_item["localization"]["en-US"]["custom_app_name"] = subscription_group_item["custom_app_name"].to_s.strip
587
+ if !new_subscription_group_item["localization"]["en-US"]["custom_app_name"].nil? && new_subscription_group_item["localization"]["en-US"]["custom_app_name"].length > 45
588
+ puts new_subscription_group_item["localization"]["en-US"]["custom_app_name"]
589
+ raise Informative, "custom_app_name 超出限定的30个字符 !!!"
590
+ end
591
+
592
+ new_subscription_group_item["subscription_items"] = []
593
+
594
+ old_subscription_group_items = subscription_group_item["subscription_items"] || []
595
+ if old_subscription_group_items.size <= 0 && !subscription_group_item["items"].nil?
596
+ old_subscription_group_items = subscription_group_item["items"] || []
597
+ end
598
+
599
+ new_subscription_group_item["subscription_items"] = process_subscriptions_item(subscription_items:old_subscription_group_items, iap_tester:iap_tester, subscription_duration_dict:tier_json["subscription_duration"])
600
+ new_subscription_json << new_subscription_group_item
601
+ end
602
+
603
+ return new_subscription_json
604
+ end
605
+
606
+ def process_subscriptions_item(subscription_items:nil, iap_tester:nil, subscription_duration_dict:nil)
607
+
608
+
609
+ subscription_items = subscription_items || []
610
+ new_subscription_items = []
611
+ subscription_items.each do |subs_item|
612
+ new_subs_item = {}
613
+
614
+ new_subs_item["reference_name"] = subs_item["reference_name"].to_s.strip || subs_item["product_name"].to_s.strip
615
+ if !new_subs_item["reference_name"].nil? && new_subs_item["reference_name"].length > 64
616
+ puts new_subs_item["reference_name"]
617
+ raise Informative, " Reference Name 超出限定的64个字符 !!!"
618
+ end
619
+
620
+ new_subs_item["product_id"] = subs_item["product_id"].to_s.strip
621
+ temp_product_id = new_subs_item["product_id"]
622
+ if new_subs_item["product_id"].include?("PRODUCT_BUNDLE_IDENTIFIER")
623
+ temp_product_id = new_subs_item["product_id"]
624
+ temp_product_id = temp_product_id.gsub(/PRODUCT_BUNDLE_IDENTIFIER/, @bundle_id);
625
+ new_subs_item["product_id"] = temp_product_id
626
+ end
627
+
628
+ if !new_subs_item["product_id"].nil? && new_subs_item["product_id"].length > 100
629
+ puts temp_product_id
630
+ raise Informative, "product_id 超出限定的100个字符 !!!"
631
+ end
632
+
633
+ new_subs_item["available_in_all_territories"] = true
634
+ new_subs_item["family_sharable"] = false
635
+ new_subs_item["price"] = subs_item["price"].to_s.strip
636
+ new_subs_item["subscription_duration"] = subscription_duration_dict[subs_item["subscription_duration"]]
637
+
638
+ if !subs_item["subscription_free_trial"].nil?
639
+ new_subs_item["subscription_free_trial"] = subs_item["subscription_free_trial"]
640
+ end
641
+ new_subs_item["localization"] = {}
642
+ new_subs_item["localization"]["en-US"] = {}
643
+ new_subs_item["localization"]["en-US"]["locale"] = "en-US"
644
+ new_subs_item["localization"]["en-US"]["display_name"] = subs_item["display_name"].to_s.strip
645
+ if !new_subs_item["localization"]["en-US"]["display_name"].nil? && new_subs_item["localization"]["en-US"]["display_name"].length > 30
646
+ puts temp_product_id
647
+ raise Informative, "Display Name 超出限定的30个字符 !!!"
648
+
649
+ end
650
+ new_subs_item["localization"]["en-US"]["description"] = subs_item["description"].to_s.strip
651
+ if !new_subs_item["localization"]["en-US"]["description"].nil? && new_subs_item["localization"]["en-US"]["description"].length > 45
652
+ puts temp_product_id
653
+ raise Informative, "Description 超出限定的45个字符 !!!"
654
+ end
655
+
656
+ new_subs_item["review_note"] = "username: " + iap_tester["email"] + " password: " + iap_tester["password"]
657
+ if !subs_item["review_screenshot"].nil?
658
+ new_subs_item["review_screenshot"] = File.basename(subs_item["review_screenshot"])
659
+ new_subs_item["review_screenshot_fullpath"] = File.join(@iapconfig_dir, new_subs_item["review_screenshot"])
660
+ else
661
+ new_subs_item["review_screenshot_fullpath"] = nil
662
+ end
663
+
664
+
665
+ new_subscription_items << new_subs_item
666
+ end
667
+
668
+ return new_subscription_items
669
+ end
670
+
671
+
672
+ def itc_has_sandbox_tester(email:nil)
673
+
674
+ has_sandbox_account = false
675
+ begin
676
+ response = @app_store_connect.list_sandbox_testers()
677
+ # puts JSON.pretty_generate(response)
678
+ has_sandbox_account = false
679
+ response[:data].each do |tester_item|
680
+ if tester_item[:attributes][:ac_account_name].to_s.eql?(@iaptester['email'])
681
+ has_sandbox_account = true
682
+ # puts "itc_has_sandbox_tester SandboxTester : " + @iaptester['email'] + "账号已经存在"
683
+ end
684
+ end
685
+ rescue => err
686
+
687
+ end
688
+
689
+ if !has_sandbox_account
690
+
691
+ begin
692
+ testers = Spaceship::ConnectAPI::SandboxTester.all()
693
+ if !testers.nil? && testers.size > 0
694
+ testers.each do | tester_item |
695
+ # puts tester_item.email
696
+ if tester_item.email.to_s.eql?(@iaptester['email'])
697
+ has_sandbox_account = true
698
+ # puts "itc_has_sandbox_tester ConnectAPI SandboxTester : " + @iaptester['email'] + "账号已经存在"
699
+ end
700
+ end
701
+ end
702
+ rescue => err
703
+
704
+ end
705
+
706
+ end
707
+
708
+ return has_sandbox_account
709
+ end
710
+
711
+
712
+
713
+ def create_iap_tester_ex( )
714
+ b_create_success = false
715
+ testers = nil
716
+ time = Time.local(1995 - rand(10), 12 - rand(10), 25- rand(20))
717
+ birth_date = time.strftime("%Y-%m-%d")
718
+ begin
719
+ testers = Spaceship::ConnectAPI::SandboxTester.create(
720
+ first_name: @iaptester['first_name'],
721
+ last_name: @iaptester['last_name'],
722
+ email: @iaptester['email'],
723
+ password: @iaptester['password'],
724
+ confirm_password: @iaptester['password'],
725
+ secret_question: @iaptester['password'] + '1234?',
726
+ secret_answer: @iaptester['password'],
727
+ birth_date:birth_date,
728
+ app_store_territory: @iaptester['territory'] || 'USA'
729
+ )
730
+ rescue => err
731
+ b_create_success = false
732
+ end
733
+
734
+ # if !b_create_success || testers.nil?
735
+ # begin
736
+ # testers = Spaceship::Tunes::SandboxTester.create!(
737
+ # email: @iaptester['email'],
738
+ # password: @iaptester['password'],
739
+ # first_name: @iaptester['first_name'],
740
+ # last_name: @iaptester['last_name'],
741
+ # country: @iaptester['territory'] || 'USA'
742
+ # )
743
+ # puts testers
744
+ # rescue => err
745
+
746
+ # end
747
+ # end
748
+
749
+ end
750
+
751
+
752
+ def create_iap_tester()
753
+
754
+ sandbox_tester_file = File.join(@iapconfig_dir, "iap_tester.json")
755
+ sandbox_tester_json = {}
756
+ if File.exist?(sandbox_tester_file)
757
+ sandbox_tester_json = JSON.parse(File.read(sandbox_tester_file))
758
+ else
759
+ raise Informative, "缺少沙盒配置文件iap_tester.json !!!"
760
+ end
761
+
762
+ @iaptester = nil
763
+ if !sandbox_tester_json.nil? && !sandbox_tester_json["iaptester"].nil?
764
+ @iaptester = sandbox_tester_json["iaptester"]
765
+ end
766
+
767
+ if !@iaptester.nil? && @iaptester["need_create"]
768
+ puts
769
+ puts
770
+ if itc_has_sandbox_tester(email:@iaptester["email"])
771
+
772
+ puts "已存在沙盒账号:#{@iaptester["email"]},无需创建!!!"
773
+ else
774
+ puts "没有沙盒账号:#{@iaptester["email"]},需新创建!!!"
775
+
776
+ create_iap_tester_ex()
777
+ if itc_has_sandbox_tester(email:@iaptester["email"])
778
+ puts "沙盒账号:#{@iaptester["email"]},创建成功!!!"
779
+ else
780
+ raise Informative, "沙盒账号:#{@iaptester["email"]},创建失败!!!"
781
+ end
782
+ end
783
+ else
784
+ raise Informative, "没有Sandbox Tester !!!"
785
+ end
786
+
787
+ end
788
+
789
+
790
+
791
+
792
+
793
+ end
794
+ end
795
+ end
796
+ end