[
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n#   For a library or package, you might want to ignore these files since the code is\n#   intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# poetry\n#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.\n#   This is especially recommended for binary packages to ensure reproducibility, and is more\n#   commonly ignored for libraries.\n#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control\n#poetry.lock\n\n# pdm\n#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.\n#pdm.lock\n#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it\n#   in version control.\n#   https://pdm.fming.dev/#use-with-ide\n.pdm.toml\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Cython debug symbols\ncython_debug/\n\n# PyCharm\n#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can\n#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore\n#  and can be added to the global gitignore or merged into this file.  For a more nuclear\n#  option (not recommended) you can uncomment the following to ignore the entire idea folder.\n#.idea/\n\n**/credentials.txt\n**/pool_token.txt\n**/tokens.json\n**/config.json\n**/license.jwt"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 沐风\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Readme.md",
    "content": "# 基于pandora的ChatGPT API\n\n## 说明\n\n感谢[pandora](https://github.com/pandora-next)项目，这一次真正实现了ChatGPT自由。本项目主要实现了根据账号密码自动获取accessToken并更新至pool-token。初始脚本来源于旧的pandora，该库被删除，无法添加链接，这个脚本是我进行修改过后的，使用更方便。本人也是小白一枚，欢迎大家一起补充完善。\n\n建了一个Q群：698974728，有问题可以在这里说\n\n2023/12/11 重写了脚本，不再需要Python环境\n\n**如果对您有帮助，请给一个免费的star，谢谢！**\n\n## 写在前面\n\n我们的目标是获得一个 `ChatGPT API Key`，通常是在使用`ChatGPT`的衍生项目时使用，比如[ChatGPT-Next-Web](https://github.com/Yidadaa/ChatGPT-Next-Web)、[gpt_academic](https://github.com/binary-husky/gpt_academic)等。这些项目需要我们提供一个 `API Key` 及其对应的 `APIUrl`。\n\n通过使用本项目的脚本，我们将获得一个 `pk-xxxxxxx` 格式的`api key`。`APIUrl`则为你部署的`PandoraNext`地址\n\n### 大致流程\n\n准备账号密码 => 获取 `Access Token` => 获取 `Share Token` => 获取 `Pool Token`\n\n**`Pool Token` 就是我们最后需要的 `api key`。**\n\n### 简单说明\n\n`Access Token`是 OpenAI 官方的用户鉴权信息，相当于用户的唯一标识了，直接使用`Access Token`和使用官方key一样会扣额度，`Access Token`有效期是14天，所以我们至少要14天运行一次脚本。\n\n`Share Token` 和 `Pool Token` 均是由 pandora 作者提供的服务，与官方无关。`Share Token`可以实现多人共享一个账号，可以进行会话隔离，不会扣除额度，实现了ChatGPT自由。但是`Share Token`依旧存在 `1` 个会话的限制，所以作者提供了 `Pool Token`，使用由最多 `100` 个`Share Token`组合的 `Pool Token` 时会自动轮转，实现了多人同时会话。\n\n更多信息可以查看[pandora文档](https://fakeopen.org/PandoraNext/)\n\n### 文件说明\n\n`demo`目录下存放了各环境的示例，本项目是通过`scripts`下的文件实现功能的。\n\n- `run_job.bat` windows执行脚本的批处理脚本\n- `add_auto_run_job.bat` 添加定时任务的批处理脚本\n- `update_pool_token.*` 实现功能的脚本。\n- `credentials.txt` 存储账号、密码\n- `pool_token.txt` 存储 `Pool Token`\n\n## 使用方法\n\n## 部署PandoraNext\n\n首先你需要参考[PandoraNext文档](https://fakeopen.org/PandoraNext/)进行部署，本项目的脚本无需与PandoraNext在同一位置。如果你怕出问题，就按照demo一样，将本项目的`scripts`文件夹放在`PandoraNext`目录下。\n\n**部署PandoraNext时，你至少应配置`config.json`中的 `bind`,`license_id`、`proxy_api_prefix`**\n```\n示例\n{\n    \"bind\": \"0.0.0.0:8181\",\n    \"license_id\": \"xxxxxxxxxxx\",\n    \"proxy_api_prefix\": \"qqrr123123\",\n}\n```\n\n我们在使用 `api key` 时需要将反代url设置为`http(s)://<bind>/<proxy_api_prefix>`\n\n如: `http://127.0.0.1:8181/qqrr123123`\n\n### 自动更新pool token脚本\n\n1. 下载[scripts](https://github.com/mufeng510/Free-ChatGPT-API/tree/master/demo/)到你本地\n\n2. 打开`update_pool_token`文件，修改`$api_url`为`http(s)://<bind>/<proxy_api_prefix>`。\n\n3. 新建`credentials.txt`并设置内容为账号密码，一行一个，账号密码用逗号分隔\n\n```\nxxx@outlook.com,xxxxxx\nxxx@outlook.com,xxxxxx\n```\n\n4. 新建`pool_token.txt`并设置内容为你的pool tohen (可选，没有会自动生成)\n\n```\npk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n```\n\npool tohen设置一次后就不会再变了，以后添加修改账号密码只需要执行一次脚本就行了。\n\n5. windows执行`run_job.bat` 即可，linux 执行`bash update_pool_token.sh`，\n\n如果缺少权限先执行\n\nwin: `Set-ExecutionPolicy RemoteSigned`\n\nlinux: `chmod +x update_pool_token.sh`。 \n\npool tohen最后会保存到`pool_token.txt`。\n\n<details> <summary>python额外要做的（在上述步骤之前）</summary>\n\n1. 安装python环境\n\n方法一：下载[python](https://www.python.org/downloads/)安装并设置环境变量。\n\n方法二：使用`miniconda`。\n\n- 在终端中执行：\n```\n# 使用scoop安装miniconda3 (没有scoop请手动安装miniconda)\nscoop install miniconda3\n# 创建pandora专用的环境\nconda create -n pool python=3.10\nconda init bash\nconda activate pool\n```\n\n-  打开`run_job.bat`，在`python update_pool_token.py`之前添加`call conda activate pool`\n![conda](https://github.com/mufeng510/Free-ChatGPT-API/raw/master/images/5.png)\n\n2. 安装依赖\n\n```\npip install pandora-chatgpt\n```\n</details>\n\n## 在其他项目中使用 pool token\n\n### [ChatGPT-Next-Web](https://github.com/Yidadaa/ChatGPT-Next-Web)\n\n```\nOPENAI_API_KEY: 'pk-xxxxxxxxxxxxxxxxxxxxxxxxxxxx'\nBASE_URL: 'http(s)://<bind>/<proxy_api_prefix>'\n```\n\n### [gpt_academic](https://github.com/binary-husky/gpt_academic)\n\n```\nAPI_KEY: 'pk-xxxxxxxxxxxxxxxxxxxxxxxxxxxx'\nCUSTOM_API_KEY_PATTERN : 'pk-[a-zA-Z0-9-]+$$'\nAPI_URL_REDIRECT : '{\"https://api.openai.com/v1/chat/completions\": \"http(s)://<bind>/<proxy_api_prefix>/v1/chat/completions\"}'\n```\n\n## 定时执行\n\n**windows:**\n\n运行`add_auto_run_job.bat`,默认每周二执行，想修改可以发给GPT说明你的需求进行改，添加好后可以运行一次试试有没有问题。\n\n![测试](https://github.com/mufeng510/Free-ChatGPT-API/raw/master/images/4.png)\n\n**linux:**\n\n执行`bash add_auto_run_job.sh`，每隔7天执行一次，需要删除可执行 `bash delete_auto_run_job.sh`。可在 `crontab -e` 查看\n\n### 共享站\n\nPandoraNext提供了一个功能等同[chat-shared3.zhile.io](https://chat-shared3.zhile.io/)的共享站，如果你需要保存`access_token`以供共享站使用，需要做以下修改\n\n1. 将本项目脚本放置`PandoraNext`的子目录下，如本项目demo一样\n\n2. 打开`update_pool_token`文件，取消 `Run` 方法中的 `Save-Tokens` 的注释\n\n3. 运行脚本"
  },
  {
    "path": "demo/linux_arm64/scripts/add_auto_run_job.sh",
    "content": "#!/bin/bash\n\n# 获取脚本所在的目录\nscript_dir=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\n\n# 检查是否已经存在定时任务\ncroncmd=\"$script_dir/update_pool_token.sh\"\ncronjob=\"0 0 */7 * * $croncmd\"\n\n# 检查是否已经存在定时任务\nif ! crontab -l | grep -q \"$croncmd\"; then\n  # 如果不存在，则添加定时任务\n  (crontab -l ; echo \"$cronjob\") | crontab -\n  echo \"定时任务已添加\"\nelse\n  echo \"定时任务已存在\"\nfi\n"
  },
  {
    "path": "demo/linux_arm64/scripts/delete_auto_run_job.sh",
    "content": "#!/bin/bash\n\n# 获取脚本所在的目录\nscript_dir=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\n\n# 要删除的定时任务命令\ncroncmd=\"$script_dir/update_pool_token.sh\"\n\n# 从cron中删除指定的定时任务\nif crontab -l | grep -q \"$croncmd\"; then\n  crontab -l | grep -v \"$croncmd\"  | crontab -\n  echo \"定时任务已删除\"\nelse\n  echo \"定时任务不存在\"\nfi\n"
  },
  {
    "path": "demo/linux_arm64/scripts/update_pool_token.sh",
    "content": "#!/bin/bash\n\napi_url=\"http://127.0.0.1:8181/qqrr123123\"\nunique_name=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 10)\nexpires_in=0\ncurrent_dir=$(dirname \"$(readlink -f \"\\$0\")\")\ncredentials_file=\"$current_dir/credentials.txt\"\npool_token_file=\"$current_dir/pool_token.txt\"\ntokens_file=\"$current_dir/../tokens.json\"\n\nread_credentials() {\n    credentials=()\n    while IFS=, read -r username password || [[ -n $username ]]; do\n        credentials+=(\"$username\" \"$password\")\n    done < \"$credentials_file\"\n}\n\nget_access_token() {\n    local payload=\"username=$username&password=$password\"\n    local resp=$(curl -s -X POST -d \"$payload\" \"$api_url/api/auth/login\")\n    if [[ $resp == *\"access_token\"* ]]; then\n        access_token=$(echo \"$resp\" | grep -o '\"access_token\":\"[^\"]*' | cut -d'\"' -f4)\n        echo \"$access_token\"\n    else\n        err_str=$(echo \"$resp\" | tr -d '\\n\\r' | sed 's/^[ \\t]*//;s/[ \\t]*$//')\n        echo \"Login failed: $username, $err_str\"\n    fi\n}\n\nget_share_token() {\n    local data=\"unique_name=$unique_name&access_token=$access_token&expires_in=$expires_in\"\n    local resp=$(curl -s -X POST -d \"$data\" \"$api_url/api/token/register\")\n    if [[ $resp == *\"token_key\"* ]]; then\n        share_token=$(echo \"$resp\" | grep -o '\"token_key\":\"[^\"]*' | cut -d'\"' -f4)\n        echo \"$share_token\"\n    else\n        err_str=$(echo \"$resp\" | tr -d '\\n\\r' | sed 's/^[ \\t]*//;s/[ \\t]*$//')\n        echo \"share token failed: $err_str\"\n    fi\n}\n\nread_pool_token() {\n    if [[ -f $pool_token_file ]]; then\n        pool_token=$(<\"$pool_token_file\")\n        if [[ $pool_token =~ pk-[0-9a-zA-Z_\\-]{43} ]]; then\n            echo \"已存在: pool token: $pool_token\"\n        else\n            echo \"pool token: 格式不正确，将重新生成\"\n            pool_token=\"\"\n        fi\n    else\n        pool_token=\"\"\n    fi\n}\n\nupdate_pool_token() {\n    local filtered_tokens=()\n    for token in \"${share_token_keys[@]}\"; do\n        if [[ $token =~ fk-[0-9a-zA-Z_\\-]{43} ]]; then\n            filtered_tokens+=(\"$token\")\n        fi\n    done\n\n    if [[ ${#filtered_tokens[@]} -eq 0 ]]; then\n        echo \"无可用账号，请检查后重试\"\n        return\n    fi\n\n    local data=\"share_tokens=$(printf \"%s\\n\" \"${filtered_tokens[@]}\")&pool_token=$pool_token\"\n    local resp=$(curl -s -X POST -d \"$data\" \"$api_url/api/pool/update\")\n    if [[ $resp == *\"pool_token\"* ]]; then\n        count=$(echo \"$resp\" | grep -o '\"count\":[0-9]*' | cut -d':' -f2)\n        new_pool_token=$(echo \"$resp\" | grep -o '\"pool_token\":\"[^\"]*' | cut -d'\"' -f4)\n        echo \"pool token 更新结果: count: $count pool_token: $new_pool_token\"\n        echo \"$new_pool_token\" > \"$pool_token_file\"\n    else\n        echo \"pool token 更新失败\"\n    fi\n}\n\nsave_tokens() {\n    local access_token_keys=(\"$@\")\n    tokens_data=\"{\"\n    for ((i=0; i<${#access_token_keys[@]}; i++)); do\n        tokens_data+=\"\\\"user-$(($i+1))\\\": {\\\"token\\\": \\\"${access_token_keys[$i]}\\\", \\\"shared\\\": true, \\\"show_user_info\\\": false}\"\n        if [[ $i -lt $((${#access_token_keys[@]}-1)) ]]; then\n            tokens_data+=\", \"\n        fi\n    done\n    tokens_data+=\"}\"\n\n    echo \"$tokens_data\" > \"$tokens_file\"\n}\n\ngenerate_random_string() {\n    cat /dev/urandom | tr -dc 'a-zA-Z' | fold -w \"\\$1\" | head -n 1\n}\n\nrun() {\n    read_credentials\n    declare -a access_token_keys\n    declare -a share_token_keys\n    local count=0\n\n    for ((i=0; i<${#credentials[@]}; i+=2)); do\n        username=${credentials[$i]}\n        password=${credentials[$i+1]}\n        sleep_seconds=1\n        echo \"开始休眠 $sleep_seconds 秒...\"\n        sleep $sleep_seconds\n        echo \"休眠结束，继续执行后续代码.\"\n        echo \"Login begin: $username, $((i/2+1))/$((${#credentials[@]}/2))\"\n        access_token=$(get_access_token \"$username\" \"$password\")\n        if [[ -n $access_token && $access_token != *failed* ]]; then\n            echo \"Login success.\"\n            access_token_keys+=(\"$access_token\")\n            share_token=$(get_share_token \"$access_token\")\n            echo \"$share_token\"\n            if [[ -n $share_token && $share_token != *failed* ]]; then\n                share_token_keys+=(\"$share_token\")\n            else\n                echo \"Share token retrieval failed.\"\n            fi\n        else\n            echo \"Login failed or access token retrieval failed.\"\n        fi\n    done\n\n    read_pool_token\n    update_pool_token\n    # save_tokens \"${access_token_keys[@]}\"\n}\n\nrun\n"
  },
  {
    "path": "demo/python/add_auto_run_job.bat",
    "content": "@echo off\nchcp 65001 > nul\n\nREM 获取当前目录的绝对路径\nfor %%I in (\"%~dp0.\") do set \"currentDir=%%~fI\"\n\nset \"username=%USERNAME%\"\nset \"password=\"\n\nset /p password=请输入windows登录密码（如果不需要静默执行或无密码直接按回车）：\n\nif not \"%password%\"==\"\" (\n    schtasks /create /tn \"auto-update-pool-token\" /tr \"%currentDir%\\run_job.bat\" /sc weekly /d TUE /ru %username% /rp %password%\n) else (\n    schtasks /create /tn \"auto-update-pool-token\" /tr \"%currentDir%\\run_job.bat\" /sc weekly /d TUE\n)\n\npause\n"
  },
  {
    "path": "demo/python/run_job.bat",
    "content": "chcp 65001\n@echo off\ncd %~dp0\necho ------正在执行中，请等待------\n@REM call conda activate pool\npython update_pool_token.py\necho ------执行完成------\npause\nexit\n"
  },
  {
    "path": "demo/python/update_pool_token.py",
    "content": "# -*- coding: utf-8 -*-\n\n\nimport requests\nimport random\nimport string\nimport time\nimport re\nimport json\nfrom os import path\n\n\ndef run():\n    api_url = \"http://127.0.0.1:8181/qqrr123123\";\n    unique_name = generate_random_string(10)\n    expires_in = 0\n    current_dir = path.dirname(path.abspath(__file__))\n    credentials_file = path.join(current_dir, 'credentials.txt')\n    pool_token_file = path.join(current_dir, 'pool_token.txt')\n    tokens_file = path.join(current_dir, '../tokens.json')\n\n    credentials = read_credentials(credentials_file)\n\n    count = 0\n    access_token_keys = []\n    share_token_keys = []\n    for index, credential in enumerate(credentials):\n        # 接口有限流。\n        sleep_seconds = 15\n        print(f\"开始休眠 {sleep_seconds} 秒...\")\n        time.sleep(sleep_seconds)\n        print(\"休眠结束，继续执行后续代码。\")\n\n        username, password = credential[0].strip(), credential[1].strip()\n        print('Login begin: {}, {}'.format(username, f\"{index+1}/{len(credentials)}\"))\n\n        access_token = get_access_token(api_url, username, password)\n        if access_token:\n            access_token_keys.append(access_token)\n            share_token = get_share_token(api_url, unique_name, access_token, expires_in)\n            if share_token:\n                share_token_keys.append(share_token)\n\n    pool_token = read_pool_token(pool_token_file)\n    update_pool_token(api_url, share_token_keys, pool_token, pool_token_file)\n    # save_tokens(tokens_file, access_token_keys)\n\n\ndef read_credentials(credentials_file):\n    with open(credentials_file, 'r', encoding='utf-8') as f:\n        credentials = [line.strip().split(',', 1) for line in f if ',' in line]\n    return credentials\n\ndef get_access_token(api_url, username, password):\n    payload = {'username': username, 'password': password}\n    resp = requests.post(api_url + '/api/auth/login', data=payload)\n    if resp.status_code == 200:\n        print('Login success: {}'.format(username))\n        return resp.json().get('access_token')\n    else:\n        err_str = resp.text.replace('\\n', '').replace('\\r', '').strip()\n        print('Login failed: {}, {}'.format(username, err_str))\n        return None\n\ndef get_share_token(api_url, unique_name, access_token, expires_in):\n    data = {'unique_name': unique_name, 'access_token': access_token, 'expires_in': expires_in}\n    resp = requests.post(api_url + '/api/token/register', data=data)\n    if resp.status_code == 200:\n        share_token = resp.json().get('token_key')\n        print('share token: {}'.format(share_token))\n        return share_token\n    else:\n        err_str = resp.text.replace('\\n', '').replace('\\r', '').strip()\n        print('share token failed: {}'.format(err_str))\n        return None\n\ndef read_pool_token(pool_token_file):\n    # 如果已有pool token则更新, 没有则生成。\n    if path.exists(pool_token_file):\n        with open(pool_token_file, 'r', encoding='utf-8') as f:\n            pool_token = f.read().strip()\n        if(re.compile(r'pk-[0-9a-zA-Z_\\-]{43}').match(pool_token)):\n            print('已存在: pool token: {}'.format(pool_token))\n            return pool_token\n        else:\n            print('pool token: 格式不正确，将重新生成')\n            return \"\"\n    else:\n        return \"\"\n\ndef update_pool_token(api_url, share_token_keys, pool_token, pool_token_file):\n    filtered_tokens = [token for token in share_token_keys if re.match(r'fk-[0-9a-zA-Z_\\-]{43}', token)]\n    if not filtered_tokens:\n        print('无可用账号，请检查后重试')\n        return\n\n    data = {'share_tokens': '\\n'.join(filtered_tokens), 'pool_token': pool_token}\n    resp = requests.post(api_url + '/api/pool/update', data=data)\n    if resp.status_code == 200:\n        result = resp.json()\n        print('pool token 更新结果: count:{} pool_token:{}'.format(result['count'], result['pool_token']))\n        with open(pool_token_file, 'w', encoding='utf-8') as f:\n            f.write(result['pool_token'])\n    else:\n        print('pool token 更新失败')\n\ndef save_tokens(tokens_file, access_token_keys):\n    tokens_data = {f\"user-{i+1}\": {\"token\": token, \"shared\": True, \"show_user_info\": False} for i, token in enumerate(access_token_keys)}\n    with open(tokens_file, 'w', encoding='utf-8') as f:\n        json.dump(tokens_data, f, indent=2)\n\ndef generate_random_string(length):\n    letters = string.ascii_letters\n    return ''.join(random.choice(letters) for _ in range(length))\n\nif __name__ == '__main__':\n    run()"
  },
  {
    "path": "demo/win/scripts/add_auto_run_job.bat",
    "content": "@echo off\nchcp 65001 > nul\n\nREM 获取当前目录的绝对路径\nfor %%I in (\"%~dp0.\") do set \"currentDir=%%~fI\"\n\nset \"username=%USERNAME%\"\nset \"password=\"\n\nset /p password=请输入windows登录密码（如果不需要静默执行或无密码直接按回车）：\n\nif not \"%password%\"==\"\" (\n    schtasks /create /tn \"auto-update-pool-token\" /tr \"%currentDir%\\run_job.bat\" /sc weekly /d TUE /ru %username% /rp %password%\n) else (\n    schtasks /create /tn \"auto-update-pool-token\" /tr \"%currentDir%\\run_job.bat\" /sc weekly /d TUE\n)\n\npause\n"
  },
  {
    "path": "demo/win/scripts/run_job.bat",
    "content": "chcp 65001\n@echo off\ncd %~dp0\necho ------正在执行中，请等待------\necho %~dp0\npowershell %~dp0\\update_pool_token.ps1\necho ------执行完成------\npause\nexit\n"
  },
  {
    "path": "demo/win/scripts/update_pool_token.ps1",
    "content": "$api_url = \"http://127.0.0.1:8181/xxxxxxxxx\"\n\n$unique_name = -join ((65..90) + (97..122) + (48..57) | Get-Random -Count 10 | % {[char]$_})\n$expires_in = 0\n$current_dir = Split-Path -Path $MyInvocation.MyCommand.Path\n$credentials_file = Join-Path -Path $current_dir -ChildPath 'credentials.txt'\n$pool_token_file = Join-Path -Path $current_dir -ChildPath 'pool_token.txt'\n$tokens_file = Join-Path -Path $current_dir -ChildPath '../tokens.json'\n\nfunction Run {\n    $currentDateTime = Get-Date -Format \"yyyy-MM-dd HH:mm:ss\"\n    Write-Host \"-----------------------$currentDateTime-----------------------\"\n\n    $credentials = Read-Credentials -FilePath $credentials_file\n\n    $access_token_keys = @()\n    $share_token_keys = @()\n    $count = 0\n\n    foreach ($credential in $credentials) {\n        # Interface rate limited.\n        $sleep_seconds = 5\n        Start-Sleep -Seconds $sleep_seconds\n\n        $username = $credential[0].Trim()\n        $password = $credential[1].Trim()\n\n        Write-Host \"Login begin: $username, $($count+1)/$($credentials.Count)\"\n\n        $access_token = Get-AccessToken -ApiUrl $api_url -Username $username -Password $password\n        if ($access_token) {\n            $access_token_keys += $access_token\n            $share_token = Get-ShareToken -ApiUrl $api_url -UniqueName $unique_name -AccessToken $access_token -ExpiresIn $expires_in\n            if ($share_token) {\n                $share_token_keys += $share_token\n            }\n        }\n    }\n\n    $pool_token = Read-PoolToken -PoolTokenFile $pool_token_file\n    Update-PoolToken -ApiUrl $api_url -ShareTokenKeys $share_token_keys -PoolToken $pool_token -PoolTokenFile $pool_token_file\n    # Save-Tokens -TokensFile $tokens_file -AccessTokenKeys $access_token_keys\n}\n\nfunction Read-Credentials {\n    param (\n        [string]$FilePath\n    )\n    $credentials = Get-Content -Path $FilePath | Where-Object {$_ -match ','} | ForEach-Object {$_ -split ','}\n    $credentialPairs = @()\n    for ($i = 0; $i -lt $credentials.Length; $i += 2) {\n        $credentialPairs += ,@($credentials[$i], $credentials[$i + 1])\n    }\n    return ,$credentialPairs\n}\n\n\nfunction Get-AccessToken {\n    param (\n        [string]$ApiUrl,\n        [string]$Username,\n        [string]$Password\n    )\n    $payload = @{\n        username = $Username\n        password = $Password\n    }\n    try {\n        $resp = Invoke-RestMethod -Uri ($ApiUrl + '/api/auth/login') -Method Post -Body $payload\n        if ($resp.access_token) {\n            Write-Host \"Login success\"\n            return $resp.access_token\n        }\n    }\n    catch {\n        Write-Host \"Login failed:\" $_.Exception.Message.ToString().Replace(\"`n\", \"\").Replace(\"`r\", \"\").Trim()\n        return $null\n    }\n}\n\nfunction Get-ShareToken {\n    param (\n        [string]$ApiUrl,\n        [string]$UniqueName,\n        [string]$AccessToken,\n        [int]$ExpiresIn\n    )\n    $data = @{\n        unique_name = $UniqueName\n        access_token = $AccessToken\n        expires_in = $ExpiresIn\n    }\n    try {\n        $resp = Invoke-RestMethod -Uri ($ApiUrl + '/api/token/register') -Method Post -Body $data\n        if ($resp.token_key) {\n            $share_token = $resp.token_key\n            Write-Host \"share token: $share_token\"\n            return $share_token\n        }\n    }\n    catch {\n        $err_str = $_.Exception.Message.ToString().ToString().Replace(\"`n\", \"\").Replace(\"`r\", \"\").Trim()\n        Write-Host \"share token failed: $err_str\"\n        return $null\n    }\n}\n\nfunction Read-PoolToken {\n    param (\n        [string]$PoolTokenFile\n    )\n    if (Test-Path $PoolTokenFile) {\n        $pool_token = Get-Content -Path $PoolTokenFile\n        if ($pool_token -match 'pk-[0-9a-zA-Z_\\-]{43}') {\n            Write-Host \"Already exists: pool token: $pool_token\"\n            return $pool_token\n        } else {\n            return \"\"\n        }\n    }else {\n        return \"\"\n    }\n}\n\nfunction Update-PoolToken {\n    param (\n        [string]$ApiUrl,\n        [string[]]$ShareTokenKeys,\n        [string]$PoolToken,\n        [string]$PoolTokenFile\n    )\n    $filtered_tokens = $ShareTokenKeys -match 'fk-[0-9a-zA-Z_\\-]{43}'\n    if (-not $filtered_tokens) {\n        Write-Host \"No available accounts, please check and try again\"\n        return\n    }\n\n    $data = @{\n        share_tokens = $filtered_tokens -join \"`n\"\n        pool_token = $PoolToken\n    }\n    try {\n        $resp = Invoke-RestMethod -Uri ($ApiUrl + '/api/pool/update') -Method Post -Body $data\n        if ($resp.pool_token) {\n            $result = $resp | ConvertTo-Json\n            Write-Host \"$result\"\n            Write-Host \"pool token update result: count:$($result.count) pool_token:$($result.pool_token)\"\n            Set-Content -Path $PoolTokenFile -Value $result.pool_token\n        }\n    }\n    catch {\n        $err_str = $_.Exception.Message.ToString().ToString().Replace(\"`n\", \"\").Replace(\"`r\", \"\").Trim()\n        Write-Host \"pool token update failed: $err_str\"\n    }\n}\n\nfunction Save-Tokens {\n    param (\n        [string]$TokensFile,\n        [string[]]$AccessTokenKeys\n    )\n    $tokens_data = @{}\n    for ($i=0; $i -lt $AccessTokenKeys.Count; $i++) {\n        $tokens_data[\"user-$($i+1)\"] = @{\n            token = $AccessTokenKeys[$i]\n            shared = $true\n            show_user_info = $false\n        }\n    }\n    $tokens_data | ConvertTo-Json | Set-Content -Path $TokensFile\n}\nRun\n"
  }
]