@daemux/store-automator 0.10.25 → 0.10.27
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.
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
},
|
|
6
6
|
"metadata": {
|
|
7
7
|
"description": "App Store & Google Play automation for Flutter apps",
|
|
8
|
-
"version": "0.10.
|
|
8
|
+
"version": "0.10.27"
|
|
9
9
|
},
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "store-automator",
|
|
13
13
|
"source": "./plugins/store-automator",
|
|
14
14
|
"description": "3 agents for app store publishing: reviewer, meta-creator, media-designer",
|
|
15
|
-
"version": "0.10.
|
|
15
|
+
"version": "0.10.27",
|
|
16
16
|
"keywords": [
|
|
17
17
|
"flutter",
|
|
18
18
|
"app-store",
|
package/package.json
CHANGED
|
@@ -24,10 +24,14 @@ from asc_iap_api import BASE_URL, TIMEOUT, print_api_errors
|
|
|
24
24
|
# ---------------------------------------------------------------------------
|
|
25
25
|
|
|
26
26
|
def get_subscription_availability(headers: dict, sub_id: str) -> dict | None:
|
|
27
|
-
"""Fetch current availability settings for a subscription.
|
|
27
|
+
"""Fetch current availability settings for a subscription.
|
|
28
|
+
|
|
29
|
+
Includes availableTerritories relationship so callers can check territory count.
|
|
30
|
+
"""
|
|
28
31
|
resp = requests.get(
|
|
29
32
|
f"{BASE_URL}/subscriptions/{sub_id}/subscriptionAvailability",
|
|
30
33
|
headers=headers,
|
|
34
|
+
params={"include": "availableTerritories", "limit[availableTerritories]": 200},
|
|
31
35
|
timeout=TIMEOUT,
|
|
32
36
|
)
|
|
33
37
|
if not resp.ok:
|
|
@@ -35,7 +39,28 @@ def get_subscription_availability(headers: dict, sub_id: str) -> dict | None:
|
|
|
35
39
|
return None
|
|
36
40
|
print_api_errors(resp, f"get availability for subscription {sub_id}")
|
|
37
41
|
return None
|
|
38
|
-
|
|
42
|
+
body = resp.json()
|
|
43
|
+
result = body.get("data")
|
|
44
|
+
if result:
|
|
45
|
+
result["_included_territories"] = body.get("included", [])
|
|
46
|
+
return result
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def list_all_territory_ids(headers: dict) -> list[str]:
|
|
50
|
+
"""Fetch all App Store territory IDs."""
|
|
51
|
+
url: str | None = f"{BASE_URL}/territories"
|
|
52
|
+
params: dict | None = {"limit": 200}
|
|
53
|
+
all_ids: list[str] = []
|
|
54
|
+
while url:
|
|
55
|
+
resp = requests.get(url, headers=headers, params=params, timeout=TIMEOUT)
|
|
56
|
+
if not resp.ok:
|
|
57
|
+
print_api_errors(resp, "list territories")
|
|
58
|
+
return all_ids
|
|
59
|
+
data = resp.json()
|
|
60
|
+
all_ids.extend(t["id"] for t in data.get("data", []))
|
|
61
|
+
url = data.get("links", {}).get("next")
|
|
62
|
+
params = None
|
|
63
|
+
return all_ids
|
|
39
64
|
|
|
40
65
|
|
|
41
66
|
def create_subscription_availability(
|
|
@@ -97,20 +122,28 @@ def get_subscription_prices(headers: dict, sub_id: str) -> list:
|
|
|
97
122
|
def get_price_points_for_territory(
|
|
98
123
|
headers: dict, sub_id: str, territory: str,
|
|
99
124
|
) -> list:
|
|
100
|
-
"""Get available price points for a subscription in a given territory.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
125
|
+
"""Get all available price points for a subscription in a given territory.
|
|
126
|
+
|
|
127
|
+
Uses limit=200 (ASC API max) and follows pagination links to ensure
|
|
128
|
+
higher price tiers (e.g. $9.99, $69.99) beyond the first page are included.
|
|
129
|
+
"""
|
|
130
|
+
url: str | None = f"{BASE_URL}/subscriptions/{sub_id}/pricePoints"
|
|
131
|
+
params: dict | None = {
|
|
132
|
+
"filter[territory]": territory,
|
|
133
|
+
"include": "territory",
|
|
134
|
+
"limit": 200,
|
|
135
|
+
}
|
|
136
|
+
all_points: list = []
|
|
137
|
+
while url:
|
|
138
|
+
resp = requests.get(url, headers=headers, params=params, timeout=TIMEOUT)
|
|
139
|
+
if not resp.ok:
|
|
140
|
+
print_api_errors(resp, f"get price points for {territory}")
|
|
141
|
+
return all_points
|
|
142
|
+
data = resp.json()
|
|
143
|
+
all_points.extend(data.get("data", []))
|
|
144
|
+
url = data.get("links", {}).get("next")
|
|
145
|
+
params = None # next URL already contains query parameters
|
|
146
|
+
return all_points
|
|
114
147
|
|
|
115
148
|
|
|
116
149
|
def find_price_point_by_amount(
|
|
@@ -40,6 +40,7 @@ from asc_subscription_setup import (
|
|
|
40
40
|
get_review_screenshot,
|
|
41
41
|
get_subscription_availability,
|
|
42
42
|
get_subscription_prices,
|
|
43
|
+
list_all_territory_ids,
|
|
43
44
|
reserve_review_screenshot,
|
|
44
45
|
upload_screenshot_chunks,
|
|
45
46
|
)
|
|
@@ -119,21 +120,36 @@ def sync_subscription_group(
|
|
|
119
120
|
|
|
120
121
|
|
|
121
122
|
def _sync_availability(headers: dict, sub_id: str, sub_config: dict) -> None:
|
|
122
|
-
"""Ensure subscription territory availability is configured.
|
|
123
|
+
"""Ensure subscription territory availability is configured with all territories.
|
|
124
|
+
|
|
125
|
+
If no territories are specified in config, fetches all App Store territories
|
|
126
|
+
so the subscription is available everywhere (empty list = removed from sale).
|
|
127
|
+
Also re-creates availability if existing territory count is suspiciously low.
|
|
128
|
+
"""
|
|
123
129
|
avail_config = sub_config.get("availability", {})
|
|
124
130
|
available_in_new = avail_config.get("available_in_new_territories", True)
|
|
125
131
|
territory_ids = avail_config.get("territories", [])
|
|
126
132
|
|
|
133
|
+
# If no territories specified, fetch all to make available everywhere
|
|
134
|
+
if not territory_ids:
|
|
135
|
+
territory_ids = list_all_territory_ids(headers)
|
|
136
|
+
if not territory_ids:
|
|
137
|
+
print(" WARNING: Could not fetch territories", file=sys.stderr)
|
|
138
|
+
return
|
|
139
|
+
|
|
127
140
|
existing = get_subscription_availability(headers, sub_id)
|
|
128
141
|
if existing:
|
|
129
|
-
|
|
130
|
-
|
|
142
|
+
included = existing.get("_included_territories", [])
|
|
143
|
+
if len(included) >= 100:
|
|
144
|
+
print(f" Availability OK ({len(included)} territories)")
|
|
145
|
+
return
|
|
146
|
+
print(f" Availability has only {len(included)} territories, recreating with {len(territory_ids)}")
|
|
131
147
|
|
|
132
148
|
result = create_subscription_availability(
|
|
133
149
|
headers, sub_id, territory_ids, available_in_new=available_in_new,
|
|
134
150
|
)
|
|
135
151
|
if result:
|
|
136
|
-
print("
|
|
152
|
+
print(f" Availability set ({len(territory_ids)} territories)")
|
|
137
153
|
else:
|
|
138
154
|
print(" WARNING: Failed to configure availability", file=sys.stderr)
|
|
139
155
|
|
|
@@ -178,16 +194,38 @@ def _sync_pricing(headers: dict, sub_id: str, sub_config: dict) -> None:
|
|
|
178
194
|
def _sync_review_screenshot(
|
|
179
195
|
headers: dict, sub_id: str, sub_config: dict, project_root: str,
|
|
180
196
|
) -> None:
|
|
181
|
-
"""Upload a review screenshot for the subscription if configured.
|
|
197
|
+
"""Upload a review screenshot for the subscription if configured.
|
|
198
|
+
|
|
199
|
+
Falls back to the first iPhone screenshot from fastlane/screenshots/ios/en-US/
|
|
200
|
+
when the configured path does not exist.
|
|
201
|
+
"""
|
|
182
202
|
screenshot_path = sub_config.get("review_screenshot")
|
|
183
203
|
if not screenshot_path:
|
|
184
204
|
print(" WARNING: No review_screenshot configured, skipping", file=sys.stderr)
|
|
185
205
|
return
|
|
186
206
|
|
|
187
207
|
full_path = os.path.join(project_root, screenshot_path)
|
|
208
|
+
|
|
209
|
+
# Fallback: if configured path doesn't exist, pick first iPhone screenshot
|
|
188
210
|
if not os.path.isfile(full_path):
|
|
189
|
-
|
|
190
|
-
|
|
211
|
+
fallback = None
|
|
212
|
+
ios_dir = os.path.join(project_root, "fastlane", "screenshots", "ios", "en-US")
|
|
213
|
+
if os.path.isdir(ios_dir):
|
|
214
|
+
for f in sorted(os.listdir(ios_dir)):
|
|
215
|
+
if f.lower().endswith(".png") and "iphone" in f.lower():
|
|
216
|
+
fallback = os.path.join(ios_dir, f)
|
|
217
|
+
break
|
|
218
|
+
if not fallback:
|
|
219
|
+
for f in sorted(os.listdir(ios_dir)):
|
|
220
|
+
if f.lower().endswith(".png"):
|
|
221
|
+
fallback = os.path.join(ios_dir, f)
|
|
222
|
+
break
|
|
223
|
+
if fallback:
|
|
224
|
+
full_path = fallback
|
|
225
|
+
print(f" Using fallback screenshot: {os.path.basename(full_path)}")
|
|
226
|
+
else:
|
|
227
|
+
print(f" WARNING: Screenshot not found: {full_path}", file=sys.stderr)
|
|
228
|
+
return
|
|
191
229
|
|
|
192
230
|
existing = get_review_screenshot(headers, sub_id)
|
|
193
231
|
if existing:
|