-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathset-category.js
More file actions
189 lines (167 loc) · 6.53 KB
/
Copy pathset-category.js
File metadata and controls
189 lines (167 loc) · 6.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
/**
* play-console set-category
*
* Set the App Category for an app in Google Play Console.
* Handles both "App or game" type selection and the specific category.
* This field is NOT accessible via the Google Play Developer API.
*/
import { cli, Strategy } from '@jackwener/opencli/registry';
import { appUrl, waitForPlayConsole, clickSave } from './_shared.js';
// Valid app types
const APP_TYPES = ['APPLICATION', 'GAME'];
// Common categories (Play Console uses these internal values)
const KNOWN_APP_CATEGORIES = [
'ART_AND_DESIGN', 'AUTO_AND_VEHICLES', 'BEAUTY', 'BOOKS_AND_REFERENCE',
'BUSINESS', 'COMICS', 'COMMUNICATION', 'DATING', 'EDUCATION',
'ENTERTAINMENT', 'EVENTS', 'FINANCE', 'FOOD_AND_DRINK', 'HEALTH_AND_FITNESS',
'HOUSE_AND_HOME', 'LIBRARIES_AND_DEMO', 'LIFESTYLE', 'MAPS_AND_NAVIGATION',
'MEDICAL', 'MUSIC_AND_AUDIO', 'NEWS_AND_MAGAZINES', 'PARENTING',
'PERSONALIZATION', 'PHOTOGRAPHY', 'PRODUCTIVITY', 'SHOPPING', 'SOCIAL',
'SPORTS', 'TOOLS', 'TRAVEL_AND_LOCAL', 'VIDEO_PLAYERS', 'WEATHER',
'WORD_GAMES',
];
const KNOWN_GAME_CATEGORIES = [
'GAME_ACTION', 'GAME_ADVENTURE', 'GAME_ARCADE', 'GAME_BOARD',
'GAME_CARD', 'GAME_CASINO', 'GAME_CASUAL', 'GAME_EDUCATIONAL',
'GAME_MUSIC', 'GAME_PUZZLE', 'GAME_RACING', 'GAME_ROLE_PLAYING',
'GAME_SIMULATION', 'GAME_SPORTS', 'GAME_STRATEGY', 'GAME_TRIVIA',
'GAME_WORD',
];
cli({
site: 'play-console',
name: 'set-category',
description: 'Set the app category in Store settings (NOT available via Developer API)',
domain: 'play.google.com',
strategy: Strategy.UI,
args: [
{
name: 'dev_id',
required: true,
positional: true,
help: 'Developer account ID (numeric)',
},
{
name: 'app_id',
required: true,
positional: true,
help: 'App ID (numeric)',
},
{
name: 'category',
required: true,
help: `Category value, e.g. PRODUCTIVITY, TOOLS, HEALTH_AND_FITNESS, GAME_CASUAL. Run with --list-categories to see all options.`,
},
{
name: 'app_type',
default: 'APPLICATION',
choices: ['APPLICATION', 'GAME'],
help: 'Whether this is an app or a game (default: APPLICATION)',
},
{
name: 'acc_idx',
type: 'int',
default: 0,
help: 'Google account index (default: 0)',
},
{
name: 'list_categories',
type: 'bool',
default: false,
help: 'Print all known category values and exit',
},
],
columns: ['success', 'category', 'app_type', 'message'],
func: async (page, kwargs) => {
const { dev_id, app_id, category, app_type, acc_idx, list_categories } = kwargs;
if (list_categories) {
const all = [
...KNOWN_APP_CATEGORIES.map(c => ({ type: 'APPLICATION', category: c })),
...KNOWN_GAME_CATEGORIES.map(c => ({ type: 'GAME', category: c })),
];
return all;
}
const catStr = String(category).toUpperCase();
const typeStr = String(app_type).toUpperCase();
const targetUrl = appUrl(String(dev_id), String(app_id), 'store-presence/store-settings', Number(acc_idx));
await page.goto(targetUrl, { waitUntil: 'networkidle' });
await waitForPlayConsole(page);
// Step 1: Set "App or Game" type if visible
const typeSet = await page.evaluate((appType) => {
// Play Console shows a radio group or select for Application vs Game
const radioSelectors = [
`mat-radio-button[value="${appType}"]`,
`input[type="radio"][value="${appType}"]`,
`[data-value="${appType}"]`,
];
for (const sel of radioSelectors) {
const radio = document.querySelector(sel);
if (radio) {
radio.click();
radio.dispatchEvent(new Event('change', { bubbles: true }));
return true;
}
}
return false;
}, typeStr);
await page.waitForTimeout(800);
// Step 2: Select the category from the dropdown
// Play Console uses mat-select for category
const catSet = await page.evaluate((cat) => {
// Find the category select — it's typically the first/only mat-select
// after "App or game" on the Store settings page
const selects = Array.from(document.querySelectorAll('mat-select'));
// Find the one labeled "Category" or "App category"
const categorySelect = selects.find(s => {
const label = s.closest('mat-form-field')?.querySelector('mat-label, label');
return label?.textContent?.toLowerCase().includes('categor');
}) ?? selects[0];
if (!categorySelect) return { found: false };
// Click to open the dropdown
categorySelect.click();
return { found: true, opened: true };
}, catStr);
if (!catSet.found) {
throw new Error(
'Could not find category dropdown on Store settings page. ' +
'Make sure the page loaded correctly and you are on the Store settings section.'
);
}
// Wait for the dropdown panel to open
await page.waitForSelector('mat-option, .mat-option', { timeout: 5000 });
await page.waitForTimeout(300);
// Click the matching option
const optionClicked = await page.evaluate((cat) => {
const options = Array.from(document.querySelectorAll('mat-option, .mat-option'));
// Try exact value match first
const exactMatch = options.find(o =>
o.getAttribute('value') === cat ||
o.getAttribute('data-value') === cat
);
if (exactMatch) { exactMatch.click(); return { clicked: true, method: 'value' }; }
// Try text content match (category display names)
const catLower = cat.toLowerCase().replace(/_/g, ' ');
const textMatch = options.find(o =>
o.textContent?.toLowerCase().replace(/_/g, ' ').includes(catLower)
);
if (textMatch) { textMatch.click(); return { clicked: true, method: 'text', text: textMatch.textContent?.trim() }; }
return { clicked: false, available: options.map(o => o.textContent?.trim()).filter(Boolean) };
}, catStr);
if (!optionClicked.clicked) {
const available = optionClicked.available?.slice(0, 20).join(', ');
throw new Error(
`Category "${catStr}" not found in dropdown. Available options: ${available}. ` +
`Use --list-categories for all known values, or check Play Console directly.`
);
}
await page.waitForTimeout(500);
await clickSave(page);
return [{
success: true,
category: catStr,
app_type: typeStr,
message: optionClicked.method === 'text'
? `Category set via text match: "${optionClicked.text}". Verify in Play Console.`
: 'Category saved successfully',
}];
},
});