import requests import json import time import os import sys import re from typing import Dict, List, Optional, Tuple import argparse from datetime import datetime import colorama from colorama import Fore, Style, Back import configparser from rich.console import Console from rich.markdown import Markdown import signal # 初始化色彩支持 colorama.init() console = Console() class DeepSider2Client: """ DeepSider 2 API 客户端,支持流式输出和多轮对话 """ BASE_URL = "https://api.chargpt.ai/api/v2" DEFAULT_MODEL = "anthropic/claude-3.7-sonnet" # 支持的模型列表 MODELS = [ "anthropic/claude-3.7-sonnet", "anthropic/claude-3.5-sonnet", "openai/gpt-4o", "openai/o1", "openai/o3-mini", "openai/gpt-4o-mini", "google/gemini-2.0-flash", "google/gemini-2.0-pro-exp-02-05", "google/gemini-2.0-flash-thinking-exp-1219", "x-ai/grok-3", "x-ai/grok-3-reasoner", "deepseek/deepseek-chat", "deepseek/deepseek-r1", "qwen/qwq-32b", "qwen/qwen-max" ] def __init__(self, token=None, config_file="config.ini"): """ 初始化客户端 Args: token: Bearer 令牌,如果为None则尝试从配置文件加载 config_file: 配置文件路径 """ self.token = token self.config_file = config_file self.conversation_id = None self.question_id = None self.answer_id = None self.model = self.DEFAULT_MODEL self.current_title = "新对话" self.headers = { "accept": "*/*", "accept-encoding": "gzip, deflate, br, zstd", "accept-language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7", "content-type": "application/json", "origin": "chrome-extension://client", "i-lang": "zh-CN", "i-version": "1.1.64", "sec-ch-ua": '"Chromium";v="134", "Not:A-Brand";v="24"', "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": "Windows", "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "cross-site", "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36" } # 加载配置 self.load_config() if self.token: self.headers["authorization"] = f"Bearer {self.token}" # 创建目录存储对话历史 os.makedirs("conversations", exist_ok=True) def load_config(self): """加载配置文件""" config = configparser.ConfigParser() # 检查配置文件是否存在 if os.path.exists(self.config_file): config.read(self.config_file, encoding='utf-8') # 读取认证令牌 if 'Auth' in config and 'token' in config['Auth'] and not self.token: self.token = config['Auth']['token'] # 读取模型设置 if 'Settings' in config and 'model' in config['Settings']: self.model = config['Settings']['model'] else: # 创建默认配置 config['Auth'] = {'token': self.token or ''} config['Settings'] = {'model': self.DEFAULT_MODEL} # 保存配置 with open(self.config_file, 'w', encoding='utf-8') as f: config.write(f) def save_config(self): """保存配置到文件""" config = configparser.ConfigParser() # 更新配置 config['Auth'] = {'token': self.token or ''} config['Settings'] = {'model': self.model} # 写入文件 with open(self.config_file, 'w', encoding='utf-8') as f: config.write(f) def set_token(self, token): """ 设置新的认证令牌 Args: token: 新的Bearer令牌 """ self.token = token self.headers["authorization"] = f"Bearer {token}" self.save_config() console.print(f"[green]已设置新的认证令牌[/green]") def set_model(self, model): """ 设置使用的模型 Args: model: 模型标识符 """ # 检查模型是否在支持列表中 if model in self.MODELS: self.model = model self.save_config() console.print(f"[green]已设置模型为: {model}[/green]") else: # 显示可用模型列表 console.print(f"[red]错误: 未知模型 '{model}'[/red]") self.list_models() def list_models(self): """列出所有支持的模型""" console.print("\n[bold]支持的模型列表:[/bold]") table = Table(box=box.ROUNDED) table.add_column("序号", style="cyan") table.add_column("模型", style="green") for i, model in enumerate(self.MODELS): table.add_row(f"{i+1}", model) console.print(table) console.print(f"当前使用模型: [bold cyan]{self.model}[/bold cyan]") def check_token(self): """检查是否设置了有效的令牌""" if not self.token: console.print("[red]错误: 未设置认证令牌。请使用 'token' 命令设置令牌。[/red]") return False return True def get_quota(self): """ 获取账户余额信息 Returns: dict: 返回余额信息 """ if not self.check_token(): return None try: response = requests.get( f"{self.BASE_URL.replace('/v2', '')}/quota/retrieve", headers=self.headers ) if response.status_code == 200: data = response.json() return data else: console.print(f"[red]获取余额失败: {response.status_code} - {response.text}[/red]") return None except Exception as e: console.print(f"[red]获取余额出错: {str(e)}[/red]") return None def display_quota(self): """显示账户余额信息""" quota_data = self.get_quota() if not quota_data or quota_data.get('code') != 0: return False try: quota_list = quota_data.get('data', {}).get('list', []) if quota_list: quota_panel_content = "" for item in quota_list: item_type = item.get('type', '') total = item.get('total', 0) available = item.get('available', 0) title = item.get('title', '') unit = item.get('unit', '') unit_text = f" {unit}" if unit else "" quota_panel_content += f"{title} ({item_type}): 可用 {available}/{total}{unit_text}\n" plan_name = quota_data.get('data', {}).get('showPlanName', '') if plan_name: quota_panel_content += f"当前套餐: {plan_name}" console.print(Panel(quota_panel_content, title="账户余额信息", border_style="blue")) return True else: console.print("[yellow]未获取到余额信息[/yellow]") return False except Exception as e: console.print(f"[red]解析余额数据出错: {str(e)}[/red]") return False def send_options_request(self, endpoint): """ 发送OPTIONS预检请求 Args: endpoint: API端点路径 Returns: bool: 预检请求是否成功 """ try: response = requests.options( f"{self.BASE_URL}/{endpoint}", headers=self.headers ) return response.status_code == 200 except Exception as e: console.print(f"[red]预检请求失败: {str(e)}[/red]") return False def create_conversation(self, prompt, web_access="close"): """ 创建新的对话 Args: prompt: 用户输入的提示 web_access: 是否启用网络访问 ("open" 或 "close") Returns: bool: 是否成功创建对话 """ if not self.check_token(): return False # 发送预检请求 if not self.send_options_request("chat/conversation"): return False # 构建请求体 payload = { "model": self.model, "prompt": prompt, "webAccess": web_access, "timezone": "Asia/Shanghai" # 使用本地时区 } # 如果是继续对话,添加对话ID if self.conversation_id: payload["clId"] = self.conversation_id try: # 发送请求,使用stream=True接收流式响应 response = requests.post( f"{self.BASE_URL}/chat/conversation", headers=self.headers, json=payload, stream=True ) # 用于存储收到的完整响应 full_response = "" thinking_content = "" current_type = None # 标记当前内容类型: "thinking" 或 "chat" # 是否已获取到ID信息 ids_received = False # 输出提示 console.print("\n[dim]DeepSider 正在响应中...[/dim]") for line in response.iter_lines(): if not line: continue # 解析SSE数据 if line.startswith(b'data: '): try: data = json.loads(line[6:].decode('utf-8')) # 处理不同类型的数据 if data.get('code') == 201: # ID信息 ids_data = data.get('data', {}) self.conversation_id = ids_data.get('clId') self.question_id = ids_data.get('qId') self.answer_id = ids_data.get('aId') ids_received = True elif data.get('code') == 202: # 内容片段 content_data = data.get('data', {}) content_type = content_data.get('type', '') # 如果类型发生变化,处理特殊情况 if current_type != content_type: if current_type == "thinking" and content_type == "chat": # 从思考切换到回答,显示完整思考内容 if thinking_content: console.print(Panel( thinking_content, title="思考过程", border_style="yellow", padding=(1, 2) )) current_type = content_type if content_type == "thinking": # 收集思考内容但不立即显示 thinking = content_data.get('thinking_content', '') if thinking: thinking_content += thinking elif content_type == "chat": # 正常对话内容,实时显示 content = content_data.get('content', '') if content: print(content, end='', flush=True) full_response += content elif data.get('code') == 203: # 完成信号 print() # 换行 console.print("[dim]回答完成[/dim]") except json.JSONDecodeError: console.print(f"[yellow]无法解析响应: {line}[/yellow]") print() # 最后添加一个换行 # 检查对话是否成功创建 if not ids_received or not self.conversation_id: console.print("[red]创建对话失败,未收到有效的对话ID[/red]") return False # 保存对话历史 self.save_conversation_history(prompt, full_response, thinking_content) # 生成对话标题 self.generate_title() # 显示余额 self.display_quota() return True except requests.RequestException as e: console.print(f"[red]请求错误: {str(e)}[/red]") return False except Exception as e: console.print(f"[red]创建对话时出错: {str(e)}[/red]") return False def generate_title(self): """为当前对话生成标题""" if not self.conversation_id: return try: # 发送预检请求 if not self.send_options_request("chat/gen-title"): return # 发送生成标题请求 response = requests.post( f"{self.BASE_URL}/chat/gen-title", headers=self.headers, json={"clId": self.conversation_id}, stream=True ) title = "" for line in response.iter_lines(): if not line: continue if line.startswith(b'data: '): try: data = json.loads(line[6:].decode('utf-8')) if data.get('code') == 202: # 标题内容 content = data.get('data', {}).get('content', '') if content: title += content except json.JSONDecodeError: pass if title: self.current_title = title # 更新对话历史文件名 self.update_conversation_title(title) console.print(f"[bold green]对话标题: {title}[/bold green]") except Exception as e: console.print(f"[yellow]生成标题时出错: {str(e)}[/yellow]") def save_conversation_history(self, prompt, response, thinking=""): """ 保存对话历史 Args: prompt: 用户输入 response: DeepSider响应 thinking: 思考过程(可选) """ if not self.conversation_id: return timestamp = datetime.now().strftime("%Y%m%d%H%M%S") filename = f"conversations/{self.conversation_id}_{timestamp}.json" # 创建或更新对话历史 if os.path.exists(filename): try: with open(filename, 'r', encoding='utf-8') as f: conversation = json.load(f) except: conversation = {"title": "", "exchanges": []} else: conversation = {"title": "", "exchanges": []} # 添加新的交流 exchange = { "time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "prompt": prompt, "response": response, "questionId": self.question_id, "answerId": self.answer_id, "model": self.model } # 如果有思考过程,也保存 if thinking: exchange["thinking"] = thinking conversation["exchanges"].append(exchange) # 保存对话 with open(filename, 'w', encoding='utf-8') as f: json.dump(conversation, f, ensure_ascii=False, indent=2) def update_conversation_title(self, title): """ 更新对话标题 Args: title: 新的对话标题 """ if not self.conversation_id: return # 查找当前对话的历史文件 pattern = f"conversations/{self.conversation_id}_*.json" import glob files = glob.glob(pattern) if files: latest_file = max(files, key=os.path.getctime) try: # 读取文件 with open(latest_file, 'r', encoding='utf-8') as f: conversation = json.load(f) # 更新标题 conversation["title"] = title # 保存更新后的内容 with open(latest_file, 'w', encoding='utf-8') as f: json.dump(conversation, f, ensure_ascii=False, indent=2) except Exception as e: console.print(f"[yellow]更新对话标题时出错: {str(e)}[/yellow]") def get_conversation_list(self, page_size=30, last_id=None): """ 获取历史对话列表 Args: page_size: 每页数量 last_id: 上一页最后一个对话的ID Returns: list: 对话列表 """ if not self.check_token(): return None try: # 构建URL url = f"{self.BASE_URL}/chat/list?pageSize={page_size}" if last_id: url += f"&lastId={last_id}" # 发送请求 response = requests.get( url, headers=self.headers ) if response.status_code == 200: data = response.json() if data.get('code') == 0: return data.get('data', {}) else: console.print(f"[red]获取对话列表失败: {data.get('message', '未知错误')}[/red]") return None else: console.print(f"[red]获取对话列表失败: {response.status_code} - {response.text}[/red]") return None except Exception as e: console.print(f"[red]获取对话列表时出错: {str(e)}[/red]") return None def list_conversations(self): """列出已保存的对话""" # 先尝试从API获取最近的对话 api_conversations = self.get_conversation_list() if api_conversations and 'list' in api_conversations: console.print("\n[bold]最近的对话:[/bold]") table = Table(box=box.ROUNDED) table.add_column("序号", style="cyan", width=6) table.add_column("标题", style="green") table.add_column("更新时间", style="yellow", width=20) table.add_column("ID", style="dim") for i, conv in enumerate(api_conversations['list']): # 转换时间戳为可读格式 update_time = datetime.fromtimestamp( int(conv.get('updateTime', 0)) / 1000 ).strftime("%Y-%m-%d %H:%M:%S") table.add_row( f"{i+1}", conv.get('title', '无标题'), update_time, conv.get('id', '') ) console.print(table) # 检查是否有更多页 if api_conversations.get('lastId') and len(api_conversations['list']) >= 30: console.print("[dim]注: 还有更多对话历史,请使用 'list_more' 命令查看更多[/dim]") return api_conversations['list'] else: # 如果API获取失败,尝试从本地文件读取 import glob files = glob.glob("conversations/*.json") if not files: console.print("[yellow]没有找到已保存的对话[/yellow]") return console.print("\n[bold]本地保存的对话:[/bold]") conversations = [] for file in files: try: with open(file, 'r', encoding='utf-8') as f: data = json.load(f) # 提取对话ID和创建时间 match = re.search(r'([a-f0-9]+)_(\d+)\.json$', file) if match: conv_id = match.group(1) timestamp = match.group(2) # 转换时间戳为可读格式 try: time_str = datetime.strptime(timestamp, "%Y%m%d%H%M%S").strftime("%Y-%m-%d %H:%M:%S") except: time_str = timestamp # 获取标题和交流次数 title = data.get("title", "无标题") exchanges = len(data.get("exchanges", [])) conversations.append({ "id": conv_id, "time": time_str, "title": title, "exchanges": exchanges, "file": file }) except: # 跳过解析错误的文件 pass # 按时间排序 conversations.sort(key=lambda x: x["time"], reverse=True) # 显示对话列表 table = Table(box=box.ROUNDED) table.add_column("序号", style="cyan", width=6) table.add_column("标题", style="green") table.add_column("交流", style="blue", width=8) table.add_column("更新时间", style="yellow", width=20) table.add_column("ID", style="dim") for i, conv in enumerate(conversations): table.add_row( f"{i+1}", conv['title'], f"{conv['exchanges']}次", conv['time'], conv['id'] ) console.print(table) return conversations def list_more_conversations(self, last_id): """ 获取更多历史对话 Args: last_id: 上一页最后一个对话的ID """ api_conversations = self.get_conversation_list(last_id=last_id) if api_conversations and 'list' in api_conversations: console.print("\n[bold]更多对话:[/bold]") table = Table(box=box.ROUNDED) table.add_column("序号", style="cyan", width=6) table.add_column("标题", style="green") table.add_column("更新时间", style="yellow", width=20) table.add_column("ID", style="dim") for i, conv in enumerate(api_conversations['list']): # 转换时间戳为可读格式 update_time = datetime.fromtimestamp( int(conv.get('updateTime', 0)) / 1000 ).strftime("%Y-%m-%d %H:%M:%S") table.add_row( f"{i+1}", conv.get('title', '无标题'), update_time, conv.get('id', '') ) console.print(table) # 检查是否有更多页 if api_conversations.get('lastId') and len(api_conversations['list']) >= 30: console.print(f"[dim]注: 还有更多对话历史,请使用 'list_more {api_conversations['lastId']}' 命令查看更多[/dim]") return api_conversations['list'] else: console.print("[yellow]无法获取更多对话历史[/yellow]") return None def load_conversation(self, conv_id): """ 加载已有对话 Args: conv_id: 对话ID Returns: bool: 是否成功加载 """ self.conversation_id = conv_id # 尝试从本地文件获取更多信息 import glob files = glob.glob(f"conversations/{conv_id}_*.json") if files: # 使用最新的文件 latest_file = max(files, key=os.path.getctime) try: with open(latest_file, 'r', encoding='utf-8') as f: conversation = json.load(f) # 显示对话信息 title = conversation.get("title", "无标题") self.current_title = title exchanges = len(conversation.get("exchanges", [])) console.print(f"[green]已加载对话: [bold]{title}[/bold] ({exchanges}次交流)[/green]") # 显示最后一次交流 if exchanges > 0: last_exchange = conversation["exchanges"][-1] console.print("\n[bold]最后一次交流:[/bold]") console.print(f"[blue]用户:[/blue] {last_exchange['prompt'][:100]}{'...' if len(last_exchange['prompt']) > 100 else ''}") console.print(f"[purple]DeepSider:[/purple] {last_exchange['response'][:100]}{'...' if len(last_exchange['response']) > 100 else ''}") # 更新问题和回答ID self.question_id = last_exchange.get("questionId") self.answer_id = last_exchange.get("answerId") # 如果有模型信息,也更新 if "model" in last_exchange: used_model = last_exchange.get("model") console.print(f"[cyan]使用模型:[/cyan] {used_model}") # 如果是合法模型,更新当前模型 if used_model in self.MODELS: self.model = used_model return True except Exception as e: console.print(f"[yellow]加载本地对话数据时出错: {str(e)}[/yellow]") # 如果没有本地文件或解析失败,只设置ID console.print(f"[green]已加载对话 ID: {conv_id}[/green]") return True def export_conversation(self, conv_id=None): """ 导出对话为Markdown格式 Args: conv_id: 要导出的对话ID,如果为None则使用当前对话 """ # 使用当前对话ID或指定的ID target_id = conv_id or self.conversation_id if not target_id: console.print("[red]没有活动的对话可导出[/red]") return import glob files = glob.glob(f"conversations/{target_id}_*.json") if not files: console.print(f"[red]未找到ID为 {target_id} 的对话[/red]") return # 使用最新的文件 latest_file = max(files, key=os.path.getctime) try: with open(latest_file, 'r', encoding='utf-8') as f: conversation = json.load(f) # 准备导出文件名 title = conversation.get("title", "对话") safe_title = re.sub(r'[\\/*?:"<>|]', "_", title) # 替换不安全的文件名字符 export_filename = f"exports/{safe_title}_{datetime.now().strftime('%Y%m%d%H%M%S')}.md" # 确保导出目录存在 os.makedirs("exports", exist_ok=True) # 创建Markdown内容 md_content = f"# {title}\n\n" md_content += f"对话ID: {target_id}\n\n" md_content += f"导出时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n" # 添加对话内容 for i, exchange in enumerate(conversation.get("exchanges", [])): md_content += f"## 交流 {i+1}\n\n" md_content += f"时间: {exchange.get('time', '')}\n\n" # 添加使用的模型信息 if "model" in exchange: md_content += f"模型: {exchange.get('model', '')}\n\n" md_content += "### 用户\n\n" md_content += f"{exchange.get('prompt', '')}\n\n" # 如果有思考过程,也导出 if "thinking" in exchange and exchange["thinking"]: md_content += "### 思考过程\n\n" md_content += f"{exchange.get('thinking', '')}\n\n" md_content += "### DeepSider\n\n" md_content += f"{exchange.get('response', '')}\n\n" md_content += "---\n\n" # 保存Markdown文件 with open(export_filename, 'w', encoding='utf-8') as f: f.write(md_content) console.print(f"[green]对话已导出到: [bold]{export_filename}[/bold][/green]") except Exception as e: console.print(f"[red]导出对话时出错: {str(e)}[/red]") def select_model_interactive(self): """通过交互方式选择模型""" self.list_models() while True: try: choice = input(f"{Fore.YELLOW}请输入模型序号 (1-{len(self.MODELS)}): {Style.RESET_ALL}") if not choice.strip(): return # 用户取消 choice = int(choice) if 1 <= choice <= len(self.MODELS): selected_model = self.MODELS[choice-1] self.set_model(selected_model) break else: console.print(f"[red]无效的选择,请输入 1-{len(self.MODELS)} 之间的数字[/red]") except ValueError: console.print("[red]请输入有效的数字[/red]") def show_help(): """显示帮助信息""" help_text = """ 命令说明: 帮助与控制: help - 显示此帮助信息 quit, exit - 退出程序 clear - 清屏 认证与设置: token <令牌> - 设置新的Bearer令牌 models - 列出支持的模型 model <模型> - 设置要使用的模型 select_model - 交互式选择模型 quota - 查看当前账户余额 对话管理: new - 开始新对话 continue - 继续当前对话 list - 列出已保存的对话 list_more
- 获取更多历史对话 load <对话ID> - 加载已有对话 export - 导出当前对话为Markdown 快捷命令: /n - 新对话 /c - 继续对话 /l - 列出对话 /m - 选择模型 /q - 查看余额 /h - 显示帮助 """ console.print(Panel(help_text, title="DeepSider 2 API 客户端帮助", border_style="cyan")) def main(): """主函数""" # 命令行参数解析 parser = argparse.ArgumentParser(description="DeepSider 2 API 交互程序") parser.add_argument("--token", help="设置Bearer令牌") parser.add_argument("--model", help="设置使用的模型") args = parser.parse_args() # 初始化客户端 client = DeepSider2Client(token=args.token) # 如果指定了模型,设置模型 if args.model: client.set_model(args.model) # 清屏 os.system('cls' if os.name == 'nt' else 'clear') # 显示欢迎信息 console.print(Panel.fit( "欢迎使用 [bold cyan]DeepSider 2 API 交互程序[/bold cyan]\n" "输入 [bold green]help[/bold green] 查看可用命令,输入 [bold red]exit[/bold red] 退出\n" "开始新对话请输入 [bold yellow]new[/bold yellow],继续当前对话请输入 [bold yellow]continue[/bold yellow]", title="DeepSider 2 客户端", border_style="blue" )) # 检查令牌并显示余额 if client.token: client.display_quota() else: console.print("[yellow]警告: 未设置认证令牌。请使用 'token <令牌>' 命令设置。[/yellow]") # 显示当前模型 console.print(f"当前使用模型: [bold cyan]{client.model}[/bold cyan]") # 主循环 while True: try: # 显示输入提示 if client.conversation_id: prompt = input(f"{Fore.GREEN}[续:{client.current_title}] {Style.RESET_ALL}> ") else: prompt = input(f"{Fore.CYAN}> {Style.RESET_ALL}") # 去除前后空白 prompt = prompt.strip() # 空输入 if not prompt: continue # 处理命令 if prompt.startswith("/"): # 快捷命令 if prompt == "/n": prompt = "new" elif prompt == "/c": prompt = "continue" elif prompt == "/l": prompt = "list" elif prompt == "/m": prompt = "select_model" elif prompt == "/q": prompt = "quota" elif prompt == "/h": prompt = "help" # 处理各类命令 if prompt.lower() in ["exit", "quit"]: console.print("[yellow]再见![/yellow]") break elif prompt.lower() == "help": show_help() elif prompt.lower() == "clear": os.system('cls' if os.name == 'nt' else 'clear') elif prompt.lower().startswith("token "): # 设置令牌 token = prompt[6:].strip() client.set_token(token) elif prompt.lower() == "models": # 列出模型 client.list_models() elif prompt.lower() == "select_model": # 交互式选择模型 client.select_model_interactive() elif prompt.lower().startswith("model "): # 设置模型 model = prompt[6:].strip() client.set_model(model) elif prompt.lower() == "quota": # 显示余额 client.display_quota() elif prompt.lower() == "new": # 新对话 client.conversation_id = None client.question_id = None client.answer_id = None client.current_title = "新对话" console.print("[green]已开始新对话[/green]") elif prompt.lower() == "continue": # 继续对话 if not client.conversation_id: console.print("[yellow]没有活动的对话可继续,请先开始新对话或加载已有对话[/yellow]") else: console.print(f"[green]继续对话: {client.current_title} (ID: {client.conversation_id})[/green]") elif prompt.lower() == "list": # 列出对话 client.list_conversations() elif prompt.lower().startswith("list_more "): # 获取更多对话 last_id = prompt[10:].strip() client.list_more_conversations(last_id) elif prompt.lower().startswith("load "): # 加载对话 conv_id = prompt[5:].strip() client.load_conversation(conv_id) elif prompt.lower() == "export": # 导出对话 client.export_conversation() else: # 发送消息到DeepSider client.create_conversation(prompt) except KeyboardInterrupt: console.print("\n[yellow]操作已取消,输入 'exit' 退出程序[/yellow]") except Exception as e: console.print(f"[red]发生错误: {str(e)}[/red]") if __name__ == "__main__": main()
x
Copy