Full Code of zsc/xiaogpt for AI

main 8539c8e63898 cached
7 files
23.4 KB
6.4k tokens
26 symbols
1 requests
Download .txt
Repository: zsc/xiaogpt
Branch: main
Commit: 8539c8e63898
Files: 7
Total size: 23.4 KB

Directory structure:
gitextract_a0v9b301/

├── .github/
│   └── workflows/
│       └── CI.yaml
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── requirements.txt
└── xiaogpt.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/CI.yaml
================================================
name: CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
  workflow_dispatch:
  
jobs:
  testing:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: install python 3.9 
        uses: actions/setup-python@v4
        with:
          python-version: '3.9'
          cache: 'pip' # caching pip dependencies
      - name: Check formatting (black)
        run: |
            pip install black
            black . --check


================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/
.idea/
xiaogptconfig.json
xiao_config.json
xiao_config.json.example
xiao_config.json.example


================================================
FILE: Dockerfile
================================================
FROM python:3.10
WORKDIR /app
RUN pip install aiohttp
COPY . .
RUN pip install --no-cache-dir -r requirements.txt
ENV OPENAI_API_KEY=$OPENAI_API_KEY
ENV XDG_CONFIG_HOME=/config
VOLUME /config
ENTRYPOINT ["python3","xiaogpt.py"]

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2023 yihong

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# xiaogpt
Play ChatGPT with Xiaomi AI Speaker

![image](https://user-images.githubusercontent.com/15976103/220028375-c193a859-48a1-4270-95b6-ef540e54a621.png)

## Update by zsc
这个是可行命令
`python3 xiaogpt.py --hardware LX04 --use_chatgpt_api --use_command`


## 一点原理

[不用 root 使用小爱同学和 ChatGPT 交互折腾记](https://github.com/yihong0618/gitblog/issues/258)


## 准备

1. ChatGPT id
2. 小爱音响
3. 能正常联网的环境或 proxy
4. python3.8+

## 使用

1. pip install aiohttp # 解决 miserver 依赖
2. pip install -r requirements.txt
3. 参考 [MiService](https://github.com/Yonsm/MiService) 项目 README 并在本地 terminal 跑 `micli list` 拿到你音响的 DID 成功 **别忘了设置 export MI_DID=xxx** 这个 MI_DID 用 
4. 参考 [revChatGPT](https://github.com/acheong08/ChatGPT) 项目 README 配置 chatGPT 的 config
5. run `python xiaogpt.py --hardware ${your_hardware}` hardware 你看小爱屁股上有型号,输入进来
6. 跑起来之后就可以问小爱同学问题了,“帮我"开头的问题,会发送一份给 ChatGPT 然后小爱同学用 tts 回答
7. 因为现在必须指定 conversation_id 和 parent_id 来持续对话,会自动建一个新的 conversation
8. 如果上面不可用,可以尝试用手机抓包,https://userprofile.mina.mi.com/device_profile/v2/conversation 找到 cookie 利用 --cookie '${cookie}' cookie 别忘了用单引号包裹
9. 默认用目前 ubus, 如果你的设备不支持 ubus 可以使用 --use_command 来使用 command 来 tts
10. 使用 --mute_xiaoai 选项,可以让小爱不回答,但会频繁请求,玩一下可以使用,不建议一直用
11. 使用 --account ‘${account}’ --password ‘${password}’ 可以不进行步骤 2
12. 如果有能力可以自行替换唤醒词,也可以去掉唤醒词,源码在 https://github.com/yihong0618/xiaogpt/blob/main/xiaogpt.py#L32
13. 可以使用 gpt-3 的 api 那样可以更流畅的对话,速度快, 请 google 如何用 openai api, 命令 --use_gpt3
14. 可以使用 --use_chatgpt_api 的 api 那样可以更流畅的对话,速度特别快,达到了对话的体验, 请 google 如何用 openai api, 命令 --use_chatgpt_api

e.g.
```shell
python3 xiaogpt.py --hardware LX06;
# or
python3 xiaogpt.py --hardware LX06 --conversation_id="xxxxxxxx";
# or 
python3 xiaogpt.py --hardware LX06 --cookie ${cookie};
# 如果你想直接输入账号密码
python3 xiaogpt.py --hardware LX06 --account ${your_xiaomi_account} --password ${your_password};
# 如果你想 mute 小米的回答
python3 xiaogpt.py --hardware LX06  --mute_xiaoai 
# 如果你想使用 gpt3 ai
export OPENAI_API_KEY=${your_api_key}
python3 xiaogpt.py --hardware LX06  --mute_xiaoai --use_gpt3
# 如果你想用 chatgpt api
export OPENAI_API_KEY=${your_api_key}
python3 xiaogpt.py --hardware LX06 --use_chatgpt_api
```

## config.json
如果想通过单一配置文件启动也是可以的, 可以通过 --config 参数指定配置文件, config 文件必须是合法的 JSON 格式
参数优先级
- cli args > default > config

```shell
python3 xiaogpt.py --config xiao_config.json
```
或者
```shell
cp xiao_config.json.example xiao_config.json
python3 xiaogpt.py 
```

## 注意

1. 请开启小爱同学的蓝牙
2. 如果要更改提示词和 PROMPT 在代码最上面自行更改
3. 目前已知 LX04 和 L05B L05C 可能需要使用 `--use_command`

## QA

1. 用破解么?不用
2. 连不上 revChatGPT?国情,你得设置 proxy 并且该地区可用的 proxy
3. 你做这玩意也没用啊?确实。。。但是挺好玩的,有用对你来说没用,对我们来说不一定呀
4. 想把它变得更好?PR Issue always welcome.
5. 还有问题?提 Issuse 哈哈

## 视频教程
https://www.youtube.com/watch?v=K4YA8YwzOOA

## Docker

### 常规用法

docker run -e OPENAI_API_KEY=< your-openapi-key > yihong0618/xiaogpt < 命令行参数 >

如

```shell
docker run -e OPENAI_API_KEY=<your-openapi-key> yihong0618/xiaogpt --account=<your-xiaomi-account> --password=<your-xiaomi-password> --hardware=<your-xiaomi-hardware> --use_chatgpt_api
```

### 使用配置文件

1.xiaogpt的配置文件可通过指定volume /config,以及指定参数--config来处理,如

```shell
docker run -e OPENAI_API_KEY=<your-openapi-key> -v <your-config-dir>:/config yihong0618/xiaogpt --account=<your-xiaomi-account> --password=<your-xiaomi-password> --hardware=<your-xiaomi-hardware> --use_chatgpt_api --config=/config/config.json
```

2.如果使用revChatGPT,则可通过指定volume /config,以及指定环境变量XDG_CONFIG_HOME来处理 ( **revChatGPT配置文件需要放置到<your-config-dir>/revChatGPT/config.json** ) ,如

```shell
docker run -e XDG_CONFIG_HOME=/config -v <your-config-dir>:/config yihong0618/xiaogpt --account=<your-xiaomi-account> --password=<your-xiaomi-password> --hardware=<your-xiaomi-hardware> --use_chatgpt_api --config=/config/config.json
```

# 感谢

- [xiaomi](https://www.mi.com/)
- @[Yonsm](https://github.com/Yonsm) 的 [MiService](https://github.com/Yonsm/MiService) 

## 赞赏

谢谢就够了


================================================
FILE: requirements.txt
================================================
rich
git+https://github.com/yihong0618/MiService
requests
revChatGPT
openai



================================================
FILE: xiaogpt.py
================================================
#!/usr/bin/env python3
import argparse
import asyncio
import json
import os
from os import environ as env
import subprocess
import time
from http.cookies import SimpleCookie
from pathlib import Path

import openai
from aiohttp import ClientSession
from miservice import MiAccount, MiNAService
from requests.utils import cookiejar_from_dict
#from revChatGPT.V1 import Chatbot, configure
from rich import print

LATEST_ASK_API = "https://userprofile.mina.mi.com/device_profile/v2/conversation?source=dialogu&hardware={hardware}&timestamp={timestamp}&limit=2"
COOKIE_TEMPLATE = "deviceId={device_id}; serviceToken={service_token}; userId={user_id}"

HARDWARE_COMMAND_DICT = {
    "LX06": "5-1",
    "L05B": "5-3",
    "S12A": "5-1",
    "LX01": "5-1",
    "L06A": "5-1",
    "LX04": "5-1",
    "L05C": "5-3",
    "L17A": "7-3",
    "X08E": "7-3",
    # add more here
}
MI_USER = ""
MI_PASS = ""
OPENAI_API_KEY = ""
KEY_WORD = "帮我"
PROMPT = "请用100字以内回答"

# simulate the response from xiaoai server by type the input.
CLI_INTERACTIVE_MODE = False


### HELP FUNCTION ###
def parse_cookie_string(cookie_string):
    cookie = SimpleCookie()
    cookie.load(cookie_string)
    cookies_dict = {}
    cookiejar = None
    for k, m in cookie.items():
        cookies_dict[k] = m.value
        cookiejar = cookiejar_from_dict(cookies_dict, cookiejar=None, overwrite=True)
    return cookiejar


class GPT3Bot:
    def __init__(self, session):
        self.api_key = OPENAI_API_KEY
        self.api_url = "https://api.openai.com/v1/completions"
        self.headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {self.api_key}",
        }
        # TODO support more models here
        self.data = {
            "prompt": "",
            "model": "text-davinci-003",
            "max_tokens": 1024,
            "temperature": 1,
            "top_p": 1,
        }
        self.session = session

    async def ask(self, query):
        # TODO Support for continuous dialogue
        # pass all prompt and answers
        # PR welcome
        self.data["prompt"] = query
        r = await self.session.post(self.api_url, headers=self.headers, json=self.data)
        return await r.json()


class ChatGPTBot:
    def __init__(self, session):
        self.session = session
        self.history = []

    async def ask(self, query):
        openai.api_key = OPENAI_API_KEY
        ms = []
        for h in self.history:
            ms.append({"role": "user", "content": h[0]})
            ms.append({"role": "assistant", "content": h[1]})
        ms.append({"role": "user", "content": f"{query}"})
        completion = openai.ChatCompletion.create(model="gpt-3.5-turbo", messages=ms)
        message = (
            completion["choices"][0]
            .get("message")
            .get("content")
            .encode("utf8")
            .decode()
        )
        self.history.append([f"{query}", message])
        # only keep 5 history
        self.history = self.history[-5:]
        return message


class MiGPT:
    def __init__(
        self,
        hardware,
        cookie="",
        use_command=False,
        mute_xiaoai=False,
        use_gpt3=False,
        use_chatgpt_api=False,
        verbose=False,
    ):
        self.mi_token_home = Path.home() / ".mi.token"
        self.hardware = hardware
        self.cookie_string = ""
        self.last_timestamp = 0  # timestamp last call mi speaker
        self.session = None
        self.chatbot = None  # a little slow to init we move it after xiaomi init
        self.user_id = ""
        self.device_id = ""
        self.service_token = ""
        self.cookie = cookie
        self.use_command = use_command
        self.tts_command = HARDWARE_COMMAND_DICT.get(hardware, "5-1")
        self.conversation_id = None
        self.parent_id = None
        self.miboy_account = None
        self.mina_service = None
        # try to mute xiaoai config
        self.mute_xiaoai = mute_xiaoai
        # mute xiaomi in runtime
        self.this_mute_xiaoai = mute_xiaoai
        # if use gpt3 api
        self.use_gpt3 = use_gpt3
        self.use_chatgpt_api = use_chatgpt_api
        self.verbose = verbose

    async def init_all_data(self, session):
        await self.login_miboy(session)
        await self._init_data_hardware()
        with open(self.mi_token_home) as f:
            user_data = json.loads(f.read())
        self.user_id = user_data.get("userId")
        self.service_token = user_data.get("micoapi")[1]
        self._init_cookie()
        await self._init_first_data_and_chatbot()

    async def login_miboy(self, session):
        self.session = session
        self.account = MiAccount(
            session,
            MI_USER,
            MI_PASS,
            str(self.mi_token_home),
        )
        # Forced login to refresh to refresh token
        await self.account.login("micoapi")
        self.mina_service = MiNAService(self.account)
        print(self.mina_service.__dict__)

    async def _init_data_hardware(self):
        if self.cookie:
            # if use cookie do not need init
            return
        hardware_data = await self.mina_service.device_list()
        for h in hardware_data:
            if h.get("hardware", "") == self.hardware:
                self.device_id = h.get("deviceID")
                break
        else:
            raise Exception(f"we have no hardware: {self.hardware} please check")

    def _init_cookie(self):
        if self.cookie:
            self.cookie = parse_cookie_string(self.cookie)
        else:
            self.cookie_string = COOKIE_TEMPLATE.format(
                device_id=self.device_id,
                service_token=self.service_token,
                user_id=self.user_id,
            )
            self.cookie = parse_cookie_string(self.cookie_string)

    async def _init_first_data_and_chatbot(self):
        data = await self.get_latest_ask_from_xiaoai()
        self.last_timestamp, self.last_record = self.get_last_timestamp_and_record(data)
        # TODO refactor this
        if self.use_gpt3:
            self.chatbot = GPT3Bot(self.session)
        elif self.use_chatgpt_api:
            self.chatbot = ChatGPTBot(self.session)
        else:
            self.chatbot = Chatbot(configure())

    async def simulate_xiaoai_question(self):
        data = {
            "code": 0,
            "message": "Success",
            "data": '{"bitSet":[0,1,1],"records":[{"bitSet":[0,1,1,1,1],"answers":[{"bitSet":[0,1,1,1],"type":"TTS","tts":{"bitSet":[0,1],"text":"Fake Answer"}}],"time":1677851434593,"query":"Fake Question","requestId":"fada34f8fa0c3f408ee6761ec7391d85"}],"nextEndTime":1677849207387}',
        }
        # Convert the data['data'] value from a string to a dictionary
        data_dict = json.loads(data["data"])
        # Get the first item in the records list
        record = data_dict["records"][0]
        # Replace the query and time values with user input
        record["query"] = input("Enter the new query: ")
        record["time"] = int(time.time() * 1000)
        # Convert the updated data_dict back to a string and update the data['data'] value
        data["data"] = json.dumps(data_dict)
        await asyncio.sleep(1)

        return data

    async def get_latest_ask_from_xiaoai(self):
        if CLI_INTERACTIVE_MODE:
            r = await self.simulate_xiaoai_question()
            return r

        r = await self.session.get(
            LATEST_ASK_API.format(
                hardware=self.hardware, timestamp=str(int(time.time() * 1000))
            ),
            cookies=parse_cookie_string(self.cookie),
        )
        return await r.json()

    def get_last_timestamp_and_record(self, data):
        if d := data.get("data"):
            records = json.loads(d).get("records")
            if not records:
                return 0, None
            last_record = records[0]
            timestamp = last_record.get("time")
            return timestamp, last_record

    async def do_tts(self, value):
        if CLI_INTERACTIVE_MODE:
            print(f"do_tts, CLI_INTERACTIVE_MODE:{value}")
            await asyncio.sleep(2)
            return

        if not self.use_command:
            try:
                await self.mina_service.text_to_speech(self.device_id, value)
            except:
                # do nothing is ok
                pass
        else:
            subprocess.check_output(["micli.py", self.tts_command, value])

    def _normalize(self, message):
        message = message.replace(" ", "--")
        message = message.replace("\n", ",")
        message = message.replace('"', ",")
        return message

    async def ask_gpt(self, query):
        if self.use_gpt3:
            return await self.ask_gpt3(query)
        elif self.use_chatgpt_api:
            return await self.ask_chatgpt_api(query)
        return await self.ask_chatgpt(query)

    async def ask_chatgpt_api(self, query):
        message = await self.chatbot.ask(query)
        message = self._normalize(message)
        return message

    async def ask_gpt3(self, query):
        data = await self.chatbot.ask(query)
        choices = data.get("choices")
        if not choices:
            print("No reply from gpt3")
        else:
            message = choices[0].get("text", "")
            message = self._normalize(message)
            return message

    async def ask_chatgpt(self, query):
        # TODO maybe use v2 to async it here
        if self.conversation_id and self.parent_id:
            data = list(
                self.chatbot.ask(
                    query,
                    conversation_id=self.conversation_id,
                    parent_id=self.parent_id,
                )
            )[-1]
        else:
            data = list(self.chatbot.ask(query))[-1]
        if message := data.get("message", ""):
            self.conversation_id = data.get("conversation_id")
            self.parent_id = data.get("parent_id")
            # xiaoai tts did not support space
            message = self._normalize(message)
            return message
        return ""

    async def get_if_xiaoai_is_playing(self):
        playing_info = await self.mina_service.player_get_status(self.device_id)
        # WTF xiaomi api
        is_playing = (
            json.loads(playing_info.get("data", {}).get("info", "{}")).get("status", -1)
            == 1
        )
        return is_playing

    async def stop_if_xiaoai_is_playing(self):
        is_playing = await self.get_if_xiaoai_is_playing()
        if is_playing:
            # stop it
            await self.mina_service.player_pause(self.device_id)

    async def run_forever(self):
        print(f"Running xiaogpt now, 用`{KEY_WORD}`开头来提问")
        async with ClientSession() as session:
            await self.init_all_data(session)
            while 1:
                if self.verbose:
                    print(
                        f"Now listening xiaoai new message timestamp: {self.last_timestamp}"
                    )
                try:
                    r = await self.get_latest_ask_from_xiaoai()
                except Exception:
                    # we try to init all again
                    await self.init_all_data(session)
                    r = await self.get_latest_ask_from_xiaoai()
                # spider rule
                if not self.mute_xiaoai:
                    await asyncio.sleep(3)
                else:
                    await asyncio.sleep(0.3)

                new_timestamp, last_record = self.get_last_timestamp_and_record(r)
                if new_timestamp > self.last_timestamp:
                    self.last_timestamp = new_timestamp
                    query = last_record.get("query", "")
                    if query.find(KEY_WORD) != -1:
                        # only mute when your clause start's with the keyword
                        if self.this_mute_xiaoai:
                            await self.stop_if_xiaoai_is_playing()
                        self.this_mute_xiaoai = False
                        # drop 帮我回答
                        query = query.replace(KEY_WORD, "")
                        query = f"{query},{PROMPT}"
                        # waiting for xiaoai speaker done
                        if not self.mute_xiaoai:
                            await asyncio.sleep(4)
                        await self.do_tts("正在问GPT请耐心等待")
                        try:
                            print(
                                "以下是小爱的回答: ",
                                last_record.get("answers")[0]
                                .get("tts", {})
                                .get("text"),
                            )
                        except:
                            print("小爱没回")
                        message = await self.ask_gpt(query)
                        # tts to xiaoai with ChatGPT answer
                        print("以下是GPT的回答: " + message)
                        await self.do_tts(message)
                        if self.mute_xiaoai:
                            while 1:
                                is_playing = await self.get_if_xiaoai_is_playing()
                                time.sleep(2)
                                if not is_playing:
                                    break
                            self.this_mute_xiaoai = True
                else:
                    if self.verbose:
                        print("No new xiao ai record")


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--hardware",
        dest="hardware",
        type=str,
        default="",
        help="小爱 hardware",
    )
    parser.add_argument(
        "--account",
        dest="account",
        type=str,
        default="",
        help="xiaomi account",
    )
    parser.add_argument(
        "--password",
        dest="password",
        type=str,
        default="",
        help="xiaomi password",
    )
    parser.add_argument(
        "--openai_key",
        dest="openai_key",
        type=str,
        default="",
        help="openai api key",
    )
    parser.add_argument(
        "--cookie",
        dest="cookie",
        type=str,
        default="",
        help="xiaomi cookie",
    )
    parser.add_argument(
        "--use_command",
        dest="use_command",
        action="store_true",
        help="use command to tts",
    )
    parser.add_argument(
        "--mute_xiaoai",
        dest="mute_xiaoai",
        action="store_true",
        help="try to mute xiaoai answer",
    )
    parser.add_argument(
        "--verbose",
        dest="verbose",
        action="store_true",
        help="show info",
    )
    parser.add_argument(
        "--use_gpt3",
        dest="use_gpt3",
        action="store_true",
        help="if use openai gpt3 api",
    )
    parser.add_argument(
        "--use_chatgpt_api",
        dest="use_chatgpt_api",
        action="store_true",
        help="if use openai chatgpt api",
    )
    parser.add_argument(
        "--config",
        dest="config",
        type=str,
        default="",
        help="config file path",
    )

    options = parser.parse_args()

    if options.config:
        config = {}
        if os.path.exists(options.config):
            with open(options.config, "r") as f:
                config = json.load(f)
        else:
            raise Exception(f"{options.config} doesn't exist")

        # update options with config
        for key, value in config.items():
            if not getattr(options, key, None):
                setattr(options, key, value)

    # if set
    MI_USER = options.account or env.get("MI_USER") or MI_USER
    MI_PASS = options.password or env.get("MI_PASS") or MI_PASS
    OPENAI_API_KEY = options.openai_key or env.get("OPENAI_API_KEY")
    if options.use_gpt3:
        if not OPENAI_API_KEY:
            raise Exception("Use gpt-3 api need openai API key, please google how to")
    if options.use_chatgpt_api:
        if not OPENAI_API_KEY:
            raise Exception("Use chatgpt api need openai API key, please google how to")

    miboy = MiGPT(
        options.hardware,
        options.cookie,
        options.use_command,
        options.mute_xiaoai,
        options.use_gpt3,
        options.use_chatgpt_api,
        options.verbose,
    )
    asyncio.run(miboy.run_forever())
Download .txt
gitextract_a0v9b301/

├── .github/
│   └── workflows/
│       └── CI.yaml
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── requirements.txt
└── xiaogpt.py
Download .txt
SYMBOL INDEX (26 symbols across 1 files)

FILE: xiaogpt.py
  function parse_cookie_string (line 45) | def parse_cookie_string(cookie_string):
  class GPT3Bot (line 56) | class GPT3Bot:
    method __init__ (line 57) | def __init__(self, session):
    method ask (line 74) | async def ask(self, query):
  class ChatGPTBot (line 83) | class ChatGPTBot:
    method __init__ (line 84) | def __init__(self, session):
    method ask (line 88) | async def ask(self, query):
  class MiGPT (line 109) | class MiGPT:
    method __init__ (line 110) | def __init__(
    method init_all_data (line 145) | async def init_all_data(self, session):
    method login_miboy (line 155) | async def login_miboy(self, session):
    method _init_data_hardware (line 168) | async def _init_data_hardware(self):
    method _init_cookie (line 180) | def _init_cookie(self):
    method _init_first_data_and_chatbot (line 191) | async def _init_first_data_and_chatbot(self):
    method simulate_xiaoai_question (line 202) | async def simulate_xiaoai_question(self):
    method get_latest_ask_from_xiaoai (line 221) | async def get_latest_ask_from_xiaoai(self):
    method get_last_timestamp_and_record (line 234) | def get_last_timestamp_and_record(self, data):
    method do_tts (line 243) | async def do_tts(self, value):
    method _normalize (line 258) | def _normalize(self, message):
    method ask_gpt (line 264) | async def ask_gpt(self, query):
    method ask_chatgpt_api (line 271) | async def ask_chatgpt_api(self, query):
    method ask_gpt3 (line 276) | async def ask_gpt3(self, query):
    method ask_chatgpt (line 286) | async def ask_chatgpt(self, query):
    method get_if_xiaoai_is_playing (line 306) | async def get_if_xiaoai_is_playing(self):
    method stop_if_xiaoai_is_playing (line 315) | async def stop_if_xiaoai_is_playing(self):
    method run_forever (line 321) | async def run_forever(self):
Condensed preview — 7 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (27K chars).
[
  {
    "path": ".github/workflows/CI.yaml",
    "chars": 477,
    "preview": "name: CI\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n  workflow_dispatch:\n  \njobs:\n  test"
  },
  {
    "path": ".gitignore",
    "chars": 1892,
    "preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
  },
  {
    "path": "Dockerfile",
    "chars": 227,
    "preview": "FROM python:3.10\nWORKDIR /app\nRUN pip install aiohttp\nCOPY . .\nRUN pip install --no-cache-dir -r requirements.txt\nENV OP"
  },
  {
    "path": "LICENSE",
    "chars": 1063,
    "preview": "MIT License\n\nCopyright (c) 2023 yihong\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
  },
  {
    "path": "README.md",
    "chars": 3862,
    "preview": "# xiaogpt\nPlay ChatGPT with Xiaomi AI Speaker\n\n![image](https://user-images.githubusercontent.com/15976103/220028375-c19"
  },
  {
    "path": "requirements.txt",
    "chars": 77,
    "preview": "rich\ngit+https://github.com/yihong0618/MiService\nrequests\nrevChatGPT\nopenai\n\n"
  },
  {
    "path": "xiaogpt.py",
    "chars": 16405,
    "preview": "#!/usr/bin/env python3\nimport argparse\nimport asyncio\nimport json\nimport os\nfrom os import environ as env\nimport subproc"
  }
]

About this extraction

This page contains the full source code of the zsc/xiaogpt GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 7 files (23.4 KB), approximately 6.4k tokens, and a symbol index with 26 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!