google-play-mcp 1.0.0
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.
- package/.env.example +2 -0
- package/README.md +208 -0
- package/bin/cli.js +111 -0
- package/locales.json +58 -0
- package/package.json +28 -0
- package/requirements.txt +5 -0
- package/server.py +1263 -0
- package/setup_key.py +212 -0
package/setup_key.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import subprocess
|
|
5
|
+
import webbrowser
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.markdown import Markdown
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.prompt import Prompt
|
|
10
|
+
|
|
11
|
+
console = Console()
|
|
12
|
+
|
|
13
|
+
def install_package(package_name):
|
|
14
|
+
"""Install a package using the current python executable."""
|
|
15
|
+
try:
|
|
16
|
+
subprocess.check_call([sys.executable, "-m", "pip", "install", package_name])
|
|
17
|
+
return True
|
|
18
|
+
except subprocess.CalledProcessError:
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
|
|
23
|
+
def load_messages():
|
|
24
|
+
"""Load messages from locales.json file."""
|
|
25
|
+
try:
|
|
26
|
+
# Determine the directory where setup_key.py is located
|
|
27
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
28
|
+
locales_path = os.path.join(script_dir, "locales.json")
|
|
29
|
+
|
|
30
|
+
with open(locales_path, "r", encoding="utf-8") as f:
|
|
31
|
+
return json.load(f)
|
|
32
|
+
except Exception as e:
|
|
33
|
+
console.print(f"[bold red]Error loading locales.json: {e}[/bold red]")
|
|
34
|
+
sys.exit(1)
|
|
35
|
+
|
|
36
|
+
MESSAGES = load_messages()
|
|
37
|
+
|
|
38
|
+
# Global language setting
|
|
39
|
+
LANG = "en"
|
|
40
|
+
TEXT = MESSAGES["en"]
|
|
41
|
+
|
|
42
|
+
def pause(msg=None):
|
|
43
|
+
if msg is None:
|
|
44
|
+
msg = TEXT["press_enter"]
|
|
45
|
+
console.input(f"\n[bold yellow]{msg}[/bold yellow]")
|
|
46
|
+
|
|
47
|
+
def copy_to_clipboard(text):
|
|
48
|
+
try:
|
|
49
|
+
import pyperclip
|
|
50
|
+
except ImportError:
|
|
51
|
+
console.print(f"{TEXT['pyperclip_install']}")
|
|
52
|
+
if install_package("pyperclip"):
|
|
53
|
+
try:
|
|
54
|
+
import pyperclip
|
|
55
|
+
except ImportError:
|
|
56
|
+
console.print(f"{TEXT['pyperclip_error']}")
|
|
57
|
+
console.print(TEXT['copy_manual'].format(text=text))
|
|
58
|
+
return
|
|
59
|
+
else:
|
|
60
|
+
console.print(f"{TEXT['copy_install_fail']}")
|
|
61
|
+
console.print(TEXT['copy_manual'].format(text=text))
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
pyperclip.copy(text)
|
|
66
|
+
console.print(TEXT['copy_success'].format(text=text))
|
|
67
|
+
except Exception as e:
|
|
68
|
+
console.print(TEXT['copy_fail'].format(e=e))
|
|
69
|
+
console.print(TEXT['copy_manual'].format(text=text))
|
|
70
|
+
|
|
71
|
+
def perform_link_step(title, instructions, url):
|
|
72
|
+
console.clear()
|
|
73
|
+
console.print(Panel.fit(f"[bold blue]{title}[/bold blue]"))
|
|
74
|
+
console.print(Markdown(instructions))
|
|
75
|
+
|
|
76
|
+
if Prompt.ask(TEXT["open_browser"], choices=["y", "n", ""], default="y", show_choices=False) != "n":
|
|
77
|
+
webbrowser.open(url)
|
|
78
|
+
|
|
79
|
+
pause()
|
|
80
|
+
|
|
81
|
+
def perform_service_account_step():
|
|
82
|
+
title = TEXT["step2_title"]
|
|
83
|
+
instructions = TEXT["step2_desc"]
|
|
84
|
+
|
|
85
|
+
url = "https://console.cloud.google.com/iam-admin/serviceaccounts"
|
|
86
|
+
|
|
87
|
+
console.clear()
|
|
88
|
+
console.print(Panel.fit(f"[bold blue]{title}[/bold blue]"))
|
|
89
|
+
console.print(Markdown(instructions))
|
|
90
|
+
|
|
91
|
+
# Define suggested values
|
|
92
|
+
sa_name = "Google Play MCP Helper"
|
|
93
|
+
sa_id = "google-play-mcp"
|
|
94
|
+
sa_desc = "Automated management of Google Play Store releases and products via MCP."
|
|
95
|
+
|
|
96
|
+
# Show suggested values - padded for easier selection
|
|
97
|
+
console.print(Panel(f"""
|
|
98
|
+
{TEXT['sa_suggested_title']}
|
|
99
|
+
|
|
100
|
+
{TEXT['sa_name_label']}
|
|
101
|
+
[cyan] {sa_name} [/cyan]
|
|
102
|
+
|
|
103
|
+
{TEXT['sa_id_label']}
|
|
104
|
+
[cyan] {sa_id} [/cyan]
|
|
105
|
+
|
|
106
|
+
{TEXT['sa_desc_label']}
|
|
107
|
+
[cyan] {sa_desc} [/cyan]
|
|
108
|
+
""", title=TEXT['sa_details_title'], border_style="green", padding=(1, 2)))
|
|
109
|
+
|
|
110
|
+
if Prompt.ask(TEXT["open_browser"], choices=["y", "n", ""], default="y", show_choices=False) != "n":
|
|
111
|
+
webbrowser.open(url)
|
|
112
|
+
|
|
113
|
+
# Interactive copy loop
|
|
114
|
+
console.print(TEXT['copy_menu_intro'])
|
|
115
|
+
while True:
|
|
116
|
+
choice = Prompt.ask(
|
|
117
|
+
TEXT['copy_menu_prompt'],
|
|
118
|
+
choices=["1", "2", ""],
|
|
119
|
+
default=""
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if choice == "1":
|
|
123
|
+
# Copy Name and ID
|
|
124
|
+
text = f"Name: {sa_name}\nID: {sa_id}"
|
|
125
|
+
copy_to_clipboard(text)
|
|
126
|
+
elif choice == "2":
|
|
127
|
+
# Copy Description
|
|
128
|
+
copy_to_clipboard(sa_desc)
|
|
129
|
+
else:
|
|
130
|
+
break
|
|
131
|
+
|
|
132
|
+
def main():
|
|
133
|
+
global LANG, TEXT
|
|
134
|
+
|
|
135
|
+
# Parse arguments for language
|
|
136
|
+
import argparse
|
|
137
|
+
parser = argparse.ArgumentParser()
|
|
138
|
+
parser.add_argument("--lang", default="en", help="Language code (en or ko)")
|
|
139
|
+
args = parser.parse_args()
|
|
140
|
+
|
|
141
|
+
if args.lang in MESSAGES:
|
|
142
|
+
LANG = args.lang
|
|
143
|
+
TEXT = MESSAGES[LANG]
|
|
144
|
+
|
|
145
|
+
# Attempt to install rich if missing, using valid pip call
|
|
146
|
+
try:
|
|
147
|
+
import rich
|
|
148
|
+
except ImportError:
|
|
149
|
+
install_package("rich")
|
|
150
|
+
# Global import after install
|
|
151
|
+
import rich
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
# Step 1: Enable API
|
|
155
|
+
perform_link_step(
|
|
156
|
+
TEXT["step1_title"],
|
|
157
|
+
TEXT["step1_desc"],
|
|
158
|
+
"https://console.cloud.google.com/apis/library/androidpublisher.googleapis.com"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# Step 2: Service Account & Key (Custom step with copy functionality)
|
|
162
|
+
perform_service_account_step()
|
|
163
|
+
|
|
164
|
+
# Step 3: Link to Play Console
|
|
165
|
+
perform_link_step(
|
|
166
|
+
TEXT["step3_title"],
|
|
167
|
+
TEXT["step3_desc"],
|
|
168
|
+
"https://play.google.com/console/users-and-permissions"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Final Configuration
|
|
172
|
+
console.clear()
|
|
173
|
+
console.print(Panel.fit(TEXT["final_config_title"]))
|
|
174
|
+
|
|
175
|
+
while True:
|
|
176
|
+
key_path = Prompt.ask(TEXT["key_path_prompt"]).strip()
|
|
177
|
+
# Handle quotes from drag-and-drop
|
|
178
|
+
if (key_path.startswith("'") and key_path.endswith("'")) or (key_path.startswith('"') and key_path.endswith('"')):
|
|
179
|
+
key_path = key_path[1:-1]
|
|
180
|
+
|
|
181
|
+
# Expand ~ to user home
|
|
182
|
+
key_path = os.path.expanduser(key_path)
|
|
183
|
+
|
|
184
|
+
if os.path.exists(key_path):
|
|
185
|
+
break
|
|
186
|
+
console.print(TEXT['key_file_error'].format(key_path=key_path))
|
|
187
|
+
|
|
188
|
+
package_name = Prompt.ask(TEXT["package_name_prompt"]).strip()
|
|
189
|
+
|
|
190
|
+
with open(".env", "w") as f:
|
|
191
|
+
f.write(f"GOOGLE_PLAY_KEY_FILE={os.path.abspath(key_path)}\n")
|
|
192
|
+
f.write(f"GOOGLE_PLAY_PACKAGE_NAME={package_name}\n")
|
|
193
|
+
|
|
194
|
+
console.print(TEXT['success_msg'].format(key_path=key_path, package_name=package_name))
|
|
195
|
+
|
|
196
|
+
if __name__ == "__main__":
|
|
197
|
+
# Ensure rich is installed before anything else requiring it
|
|
198
|
+
try:
|
|
199
|
+
import rich
|
|
200
|
+
except ImportError:
|
|
201
|
+
subprocess.check_call([sys.executable, "-m", "pip", "install", "rich"])
|
|
202
|
+
import rich
|
|
203
|
+
|
|
204
|
+
from rich.console import Console
|
|
205
|
+
from rich.panel import Panel
|
|
206
|
+
from rich.prompt import Prompt
|
|
207
|
+
from rich.markdown import Markdown
|
|
208
|
+
|
|
209
|
+
# Re-declare console to be sure
|
|
210
|
+
console = Console()
|
|
211
|
+
|
|
212
|
+
main()
|