├── pyproject.toml ├── .github └── workflows │ └── pypi-publish.yml ├── nonebot_plugin_gpt ├── source.py ├── check.py ├── config.py ├── __init__.py └── api.py ├── .gitignore ├── README.md └── LICENSE /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "nonebot-plugin-gpt" 3 | version = "1.0.3" 4 | description = "Nonebot2's plugin of ChatGPT " 5 | authors = [ 6 | {name = "nek0us", email = "nekouss@mail.com"}, 7 | ] 8 | license = {text = "GPL3"} 9 | dependencies = ["ChatGPTWeb>=0.3.3","nonebot_adapter_onebot>=2.3.1","nonebot-plugin-sendmsg-by-bots>=0.2.0","nonebot_adapter_qq>=1.3.5","nonebot2>=2.0.0","more_itertools","nonebot_plugin_htmlrender","nonebot_plugin_localstore"] 10 | requires-python = ">=3.10" 11 | readme = "README.md" 12 | 13 | [project.urls] 14 | Homepage = "https://github.com/nek0us/nonebot-plugin-gpt" 15 | Repository = "https://github.com/nek0us/nonebot-plugin-gpt" 16 | 17 | [build-system] 18 | requires = ["pdm-pep517>=0.12.0"] 19 | build-backend = "pdm.pep517.api" -------------------------------------------------------------------------------- /.github/workflows/pypi-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | pypi-publish: 11 | name: Upload release to PyPI 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@master 15 | - name: Set up Python 16 | uses: actions/setup-python@v1 17 | with: 18 | python-version: "3.x" 19 | - name: Install pypa/build 20 | run: >- 21 | python -m 22 | pip install 23 | build 24 | --user 25 | - name: Build a binary wheel and a source tarball 26 | run: >- 27 | python -m 28 | build 29 | --sdist 30 | --wheel 31 | --outdir dist/ 32 | . 33 | - name: Publish distribution to PyPI 34 | uses: pypa/gh-action-pypi-publish@release/v1 35 | with: 36 | password: ${{ secrets.PYPI_API_TOKEN }} 37 | -------------------------------------------------------------------------------- /nonebot_plugin_gpt/source.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import shutil 4 | from pathlib import Path 5 | from nonebot import require 6 | require("nonebot_plugin_localstore") 7 | import nonebot_plugin_localstore as store # noqa: E402 8 | 9 | 10 | nb_project = os.path.basename(os.getcwd()) 11 | 12 | plugin_data_dir: Path = store.get_data_dir("nonebot_plugin_gpt") 13 | data_dir = plugin_data_dir / nb_project 14 | 15 | # 需要移动的文件夹列表 16 | dirs_to_move = ["group", "private", "ban", "white", "person", "cdk", "conversation", "mdstatus.json"] 17 | 18 | # 兼容性更新 19 | if os.name == 'nt': 20 | incorrect_dir = Path(os.getcwd()) 21 | if incorrect_dir.exists() and incorrect_dir.is_dir(): 22 | for dir_name in dirs_to_move: 23 | src_dir = incorrect_dir / dir_name 24 | dest_dir = data_dir / dir_name 25 | if src_dir.exists() and src_dir.is_dir(): 26 | shutil.move(str(src_dir), str(dest_dir)) 27 | 28 | # 群聊会话 29 | grouppath = data_dir / "group" 30 | grouppath.mkdir(parents=True, exist_ok=True) 31 | grouppath = data_dir / "group" / "group.json" 32 | grouppath.touch() 33 | if not grouppath.stat().st_size: 34 | grouppath.write_text("{}") 35 | # 群聊历史会话 36 | group_conversations_path = data_dir / "group" / "group_conversations_path.json" 37 | group_conversations_path.touch() 38 | if not group_conversations_path.stat().st_size: 39 | group_conversations_path.write_text("{}") 40 | 41 | 42 | # 私聊会话 43 | privatepath = data_dir / "private" 44 | privatepath.mkdir(parents=True, exist_ok=True) 45 | privatepath = data_dir / "private" / "private.json" 46 | privatepath.touch() 47 | if not privatepath.stat().st_size: 48 | privatepath.write_text("{}") 49 | # 私聊历史会话 50 | private_conversations_path = data_dir / "private" / "private_conversations_path.json" 51 | private_conversations_path.touch() 52 | if not private_conversations_path.stat().st_size: 53 | private_conversations_path.write_text("{}") 54 | 55 | 56 | # 屏蔽词汇 57 | banpath = data_dir / "ban" 58 | banpath.mkdir(parents=True, exist_ok=True) 59 | ban_str_path = data_dir / "ban" / "ban_str.ini" 60 | ban_str_path.touch() 61 | if not ban_str_path.stat().st_size: 62 | ban_str_path.write_text("",encoding='utf-8') 63 | 64 | # 屏蔽用户 65 | banpath = data_dir / "ban" / "ban_user.json" 66 | banpath.touch() 67 | if not banpath.stat().st_size: 68 | banpath.write_text("{}") 69 | 70 | 71 | # 3.5白名单用户 72 | whitepath = data_dir / "white" 73 | whitepath.mkdir(parents=True, exist_ok=True) 74 | whitepath = whitepath / "white_list.json" 75 | whitepath.touch() 76 | if not whitepath.stat().st_size: 77 | tmp = {'group':[],'private':[],'qqgroup':[],'qqguild':[]} 78 | whitepath.write_text(json.dumps(tmp)) 79 | 80 | # plus状态存储表 81 | plusstatus = data_dir / "white" / "plus_status.json" 82 | plusstatus.touch() 83 | if not plusstatus.stat().st_size: 84 | tmp = {"status":True} 85 | plusstatus.write_text(json.dumps(tmp)) 86 | 87 | # 人设r18与归属扩展 88 | personpath = data_dir / "person" 89 | personpath.mkdir(parents=True, exist_ok=True) 90 | personpath = data_dir / "person" / "personality_user.json" 91 | personpath.touch() 92 | if not personpath.stat().st_size: 93 | personpath.write_text("{}") 94 | 95 | # gpt会话存储 96 | chatpath = data_dir / "conversation" 97 | 98 | # cdk 99 | cdkpath = data_dir / "cdk" 100 | cdkpath.mkdir(parents=True, exist_ok=True) 101 | cdklistpath = cdkpath / "cdklist.json" 102 | cdklistpath.touch() 103 | if not cdklistpath.stat().st_size: 104 | cdklistpath.write_text("{}") 105 | 106 | cdksource = cdkpath / "cdksource.json" 107 | cdksource.touch() 108 | if not cdksource.stat().st_size: 109 | cdksource.write_text("{}") 110 | 111 | # md状态存储 112 | mdstatus = data_dir / "mdstatus.json" 113 | mdstatus.touch() 114 | if not mdstatus.stat().st_size: 115 | tmp = {"group":[],"private":[]} 116 | mdstatus.write_text(json.dumps(tmp)) 117 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration 104 | poetry.toml 105 | 106 | # pdm 107 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 108 | #pdm.lock 109 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 110 | # in version control. 111 | # https://pdm.fming.dev/#use-with-ide 112 | .pdm-python 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # ruff 158 | .ruff_cache/ 159 | 160 | # LSP config files 161 | pyrightconfig.json 162 | 163 | # PyCharm 164 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 165 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 166 | # and can be added to the global gitignore or merged into this file. For a more nuclear 167 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 168 | #.idea/ 169 | 170 | # VisualStudioCode 171 | .vscode/* 172 | !.vscode/settings.json 173 | !.vscode/tasks.json 174 | !.vscode/launch.json 175 | !.vscode/extensions.json 176 | !.vscode/*.code-snippets 177 | -------------------------------------------------------------------------------- /nonebot_plugin_gpt/check.py: -------------------------------------------------------------------------------- 1 | from nonebot.adapters.onebot.v11 import MessageEvent,Message,GroupMessageEvent,PrivateMessageEvent 2 | from nonebot.adapters.qq.event import MessageEvent as QQMessageEvent 3 | from nonebot.adapters.qq.event import AtMessageCreateEvent as QQAtMessageCreateEvent 4 | from nonebot.adapters.qq.event import GroupAtMessageCreateEvent as QQGroupAtMessageCreateEvent 5 | from nonebot.adapters.qq.message import Message as QQMessage 6 | from nonebot.adapters.qq.message import MessageSegment as QQMessageSegment 7 | from nonebot.matcher import Matcher,current_matcher 8 | from nonebot.log import logger 9 | from datetime import datetime 10 | from typing import List, Literal,Dict,Tuple 11 | import json 12 | 13 | from .source import banpath,ban_str_path,whitepath,plusstatus 14 | from .config import config_gpt,config_nb 15 | 16 | 17 | # 获取id 18 | async def get_id_from_guild_group(event: QQMessageEvent): 19 | '''QQ适配器获取id(群号频道号)''' 20 | if isinstance(event,QQAtMessageCreateEvent): 21 | id = event.guild_id 22 | value = "qqguild" 23 | else: 24 | id = event.group_id # type: ignore 25 | value = "qqgroup" 26 | return id,value 27 | 28 | # 返回类型 29 | async def get_id_from_all(event: MessageEvent|QQMessageEvent) -> Tuple: 30 | '''return id,value''' 31 | if isinstance(event,GroupMessageEvent): 32 | id = str(event.group_id) 33 | value = "group" 34 | elif isinstance(event,QQMessageEvent): 35 | id,value = await get_id_from_guild_group(event) 36 | else: 37 | id = str(event.user_id) 38 | value = "private" 39 | return id,value 40 | 41 | async def plus_status(event: MessageEvent|QQMessageEvent) -> bool: 42 | if event.to_me: 43 | if isinstance(event,MessageEvent): 44 | if event.get_user_id() in config_nb.superusers: 45 | return True 46 | ban_tmp = json.loads(banpath.read_text("utf-8")) 47 | if event.get_user_id() not in ban_tmp: 48 | if not config_gpt.gptplus_white_list_mode: 49 | # 关闭gpt4白名单?那放行 50 | return True 51 | # 开了白名单?那检查plus白名单 52 | white_plus_tmp = json.loads(plusstatus.read_text("utf-8")) 53 | id,value = await get_id_from_all(event) 54 | if id in white_plus_tmp: 55 | return True 56 | return False 57 | 58 | 59 | async def gpt_rule(event: MessageEvent|QQMessageEvent) -> bool: 60 | '''gpt事件匹配规则''' 61 | if event.to_me or [gpt_start for gpt_start in config_gpt.gpt_chat_start if event.get_plaintext().startswith(gpt_start)]: 62 | ban_tmp = json.loads(banpath.read_text("utf-8")) 63 | if event.get_user_id() not in ban_tmp: 64 | # 不在黑名单?继续 65 | if not config_gpt.gpt_white_list_mode: 66 | # 关闭白名单?那放行 67 | return True 68 | # 开了白名单?那检查白名单 69 | white_tmp = json.loads(whitepath.read_text("utf-8")) 70 | # 白名单列表来 71 | id,value = await get_id_from_all(event) 72 | if id in white_tmp[value]: 73 | return True 74 | if event.get_user_id() in white_tmp["private"]: 75 | return True 76 | return False 77 | 78 | async def gpt_manage_rule(event: MessageEvent|QQMessageEvent) -> bool: 79 | '''管理事件匹配''' 80 | if event.to_me: 81 | if isinstance(event,MessageEvent): 82 | if event.get_user_id() in config_nb.superusers: 83 | return True 84 | else: 85 | id,value = await get_id_from_guild_group(event) 86 | if id in config_gpt.gpt_manage_ids: 87 | return True 88 | return False 89 | 90 | async def add_white(num: str,this_type: Literal["group", "private", "qqgroup", "qqguild"] = "group",plus: bool = False): 91 | '''添加白名单''' 92 | white_tmp: Dict[str, List[str]] = json.loads(whitepath.read_text("utf-8")) 93 | if num in white_tmp[this_type]: 94 | return "白名单已存在" 95 | if plus: 96 | plus_tmp = json.loads(plusstatus.read_text("utf-8")) 97 | plus_tmp[num] = "text-davinci-002-render-sha" 98 | plusstatus.write_text(json.dumps(plus_tmp)) 99 | white_tmp[this_type].append(num) 100 | whitepath.write_text(json.dumps(white_tmp)) 101 | return "添加成功" 102 | 103 | async def del_white(num: str,this_type: Literal["group", "private", "qqgroup", "qqguild"] = "group"): 104 | '''删除白名单''' 105 | white_tmp: Dict[str, List[str]] = json.loads(whitepath.read_text("utf-8")) 106 | if num not in white_tmp[this_type]: 107 | return "不在白名单中" 108 | plus_tmp = json.loads(plusstatus.read_text("utf-8")) 109 | if num in plus_tmp: 110 | del plus_tmp[num] 111 | plusstatus.write_text(json.dumps(plus_tmp)) 112 | white_tmp[this_type].remove(num) 113 | whitepath.write_text(json.dumps(white_tmp)) 114 | return "删除成功" 115 | 116 | async def add_ban(user:str,value:str): 117 | '''添加黑名单''' 118 | tmp = json.loads(banpath.read_text("utf-8")) 119 | if user not in tmp: 120 | tmp[user] = [] 121 | tmp[user].append(value) 122 | banpath.write_text(json.dumps(tmp)) 123 | 124 | 125 | # 黑名单关键词检索 126 | async def ban_check(event: MessageEvent|QQMessageEvent,matcher: Matcher,text: Message|QQMessage = Message()) -> None: 127 | '''检测黑名单''' 128 | ban_tmp = json.loads(banpath.read_text("utf-8")) 129 | if event.get_user_id() in ban_tmp: 130 | # 被ban了不回复 131 | await matcher.finish() 132 | ban_str_tmp = ban_str_path.read_text("utf-8").splitlines() 133 | if text.extract_plain_text(): 134 | for ban_str in ban_str_tmp: 135 | if ban_str in text.extract_plain_text(): 136 | # 触发屏蔽词 137 | current_time = datetime.now() 138 | id,value = await get_id_from_all(event) 139 | tmp = f"{current_time.strftime('%Y-%m-%d %H:%M:%S')} 在 {value} {id} 中触发屏蔽词 {ban_str}\n {text.extract_plain_text()}" 140 | logger.info(f"屏蔽词黑名单触发,屏蔽词:{ban_str}\n触发人:{event.get_user_id()}\n原语句:{tmp}") 141 | await add_ban(event.get_user_id(),tmp) 142 | await matcher.finish() -------------------------------------------------------------------------------- /nonebot_plugin_gpt/config.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, validator,model_validator 2 | from typing import List, Optional 3 | from nonebot.log import logger 4 | from nonebot import get_driver,get_plugin_config 5 | 6 | from .source import ban_str_path 7 | 8 | class Config(BaseModel): 9 | gpt_proxy: Optional[str] = None 10 | arkose_status: bool = False 11 | gpt_session: Optional[List[dict]]|str = [] 12 | group_chat: bool = True 13 | gpt_chat_start: list = [] 14 | gpt_chat_start_in_msg: bool = False 15 | begin_sleep_time: bool = False 16 | gpt_chat_priority: int = 90 17 | gpt_command_priority: int = 19 18 | gpt_white_list_mode: bool = True 19 | gptplus_white_list_mode: bool = True 20 | gpt_replay_to_replay: bool = False 21 | gpt_ban_str: Optional[List[str]]|str = [] 22 | gpt_manage_ids: list = [] 23 | gpt_lgr_markdown: bool = False 24 | gpt_httpx: bool = False 25 | gpt_url_replace: bool = False 26 | gpt_auto_init_group: bool = False 27 | gpt_auto_init_friend: bool = False 28 | gpt_init_group_pernal_name: Optional[str] = None 29 | gpt_init_friend_pernal_name: Optional[str] = None 30 | gpt_save_screen: bool = False 31 | gpt_headless: bool = True 32 | gpt_local_js: bool = False 33 | gpt_free_image: bool = False 34 | gpt_force_upgrade_model: bool = True 35 | 36 | @validator("gpt_manage_ids", always=True, pre=True) 37 | def check_gpt_manage_ids(cls,v): 38 | if isinstance(v,list): 39 | if v != []: 40 | logger.success(f"已开启 官方管理群 gpt_manage_ids {v}") 41 | else: 42 | logger.warning("gpt_manage_ids 未配置") 43 | return v 44 | else: 45 | logger.warning("gpt_manage_ids 配置错误") 46 | 47 | @validator("gpt_chat_priority", always=True, pre=True) 48 | def check_gpt_chat_priority(cls,v): 49 | if isinstance(v,int) and v >= 1: 50 | logger.success(f"已应用 聊天事件响应优先级 gpt_chat_priority {v}") 51 | return v 52 | 53 | @validator("gpt_command_priority", always=True, pre=True) 54 | def check_gpt_command_priority(cls,v): 55 | if isinstance(v,int) and v >= 1: 56 | logger.success(f"已应用 命令事件响应优先级 gpt_command_priority {v}") 57 | return v 58 | 59 | @validator("gpt_proxy") 60 | def check_gpt_proxy(cls,v): 61 | if isinstance(v,str): 62 | logger.success(f"已应用 gpt_proxy 代理配置:{v}") 63 | return v 64 | 65 | 66 | @validator("arkose_status", always=True, pre=True) 67 | def check_arkose_status(cls,v): 68 | if isinstance(v,bool): 69 | if v: 70 | logger.success("已应用 arkose_status 验证配置") 71 | else: 72 | logger.success("已关闭 arkose_status 验证配置") 73 | return v 74 | 75 | 76 | @validator("group_chat", always=True, pre=True) 77 | def check_group_chat(cls,v): 78 | if isinstance(v,bool): 79 | if v: 80 | logger.success("已开启 group_chat 多人识别配置") 81 | else: 82 | logger.success("已关闭 group_chat 多人识别配置") 83 | return v 84 | 85 | @validator("gpt_chat_start", always=True, pre=True) 86 | def check_gpt_chat_start(cls,v): 87 | if isinstance(v,list): 88 | if v: 89 | logger.success(f"已配置 gpt_chat_start 聊天前缀 {' '.join(v)}") 90 | return v 91 | 92 | @validator("gpt_chat_start_in_msg", always=True, pre=True) 93 | def check_gpt_chat_start_in_msg(cls,v): 94 | if isinstance(v,bool): 95 | if v: 96 | logger.success("已开启 gpt_chat_start_in_msg 聊天前缀加入消息") 97 | else: 98 | logger.success("已关闭 gpt_chat_start_in_msg 聊天前缀加入消息") 99 | return v 100 | 101 | @validator("begin_sleep_time", always=True, pre=True) 102 | def check_begin_sleep_time(cls,v): 103 | if isinstance(v,bool): 104 | if v: 105 | logger.success("已开启 随机延迟登录") 106 | else: 107 | logger.success("已关闭 随机延迟登录") 108 | return v 109 | 110 | @validator("gpt_session", always=True, pre=True) 111 | def check_gpt_session(cls,v): 112 | try: 113 | session_user = eval(v) 114 | if isinstance(session_user,list): 115 | num = len(session_user) 116 | v = session_user 117 | if num > 0: 118 | logger.success(f"已配置 {str(num)} 个账号信息") 119 | else: 120 | logger.warning("账号信息数量异常,请检查") 121 | return v 122 | except Exception: 123 | logger.warning("未检测到符合条件的账号信息") 124 | 125 | @model_validator(mode="after") 126 | def validate_plus(self) -> "Config": 127 | sessions = [] 128 | for session in self.gpt_session: 129 | if "gptplus" not in session: 130 | session["gptplus"] = False 131 | sessions.append(session) 132 | self.gpt_session = sessions 133 | return self 134 | 135 | @validator("gpt_white_list_mode", always=True, pre=True) 136 | def check_gpt_white_list_mode(cls,v): 137 | if isinstance(v,bool): 138 | if v: 139 | logger.success("已开启 gpt_white_list_mode 白名单模式") 140 | else: 141 | logger.success("已关闭 gpt_white_list_mode 白名单模式") 142 | return v 143 | 144 | @validator("gptplus_white_list_mode", always=True, pre=True) 145 | def check_gptplus_white_list_mode(cls,v): 146 | if isinstance(v,bool): 147 | if v: 148 | logger.success("已开启 gptplus_white_list_mode 白名单模式") 149 | else: 150 | logger.success("已关闭 gptplus_white_list_mode 白名单模式") 151 | return v 152 | 153 | @validator("gpt_replay_to_replay", always=True, pre=True) 154 | def check_gpt_replay_to_replay(cls,v): 155 | if isinstance(v,bool): 156 | if v: 157 | logger.success("已开启 gpt_replay_to_replay 回复 回复消息") 158 | else: 159 | logger.success("已关闭 gpt_replay_to_replay 回复 回复消息") 160 | return v 161 | 162 | @validator("gpt_ban_str", always=True, pre=True) 163 | def check_gpt_ban_str(cls,v): 164 | try: 165 | ban_str = eval(v) 166 | if isinstance(ban_str,list): 167 | v = ban_str 168 | if v: 169 | ban_str_path.write_text('\n'.join(v)) 170 | logger.success("已应用 gpt_ban_str 屏蔽词列表") 171 | else: 172 | logger.warning("未配置 gpt 屏蔽词") 173 | return v 174 | logger.warning("未配置 gpt 屏蔽词") 175 | except Exception: 176 | logger.warning("未配置 gpt 屏蔽词") 177 | 178 | @validator("gpt_lgr_markdown", always=True, pre=True) 179 | def check_gpt_lgr_markdown(cls,v): 180 | if isinstance(v,bool): 181 | if v: 182 | logger.success("已开启 gpt_lgr_markdown 拉格兰MarkDown转换") 183 | else: 184 | logger.success("已关闭 gpt_lgr_markdown 拉格兰MarkDown转换") 185 | return v 186 | 187 | @validator("gpt_httpx", always=True, pre=True) 188 | def check_gpt_httpx(cls,v): 189 | if isinstance(v,bool): 190 | if v: 191 | logger.success("已开启 gpt_httpx httpx使用") 192 | else: 193 | logger.success("已关闭 gpt_httpx httpx使用") 194 | return v 195 | 196 | @validator("gpt_url_replace", always=True, pre=True) 197 | def check_gpt_url_replace(cls,v): 198 | if isinstance(v,bool): 199 | if v: 200 | logger.success("已开启 gpt_url_replace QQ适配器url输出检测替换") 201 | else: 202 | logger.success("已关闭 gpt_url_replace QQ适配器url输出检测替换") 203 | return v 204 | 205 | @validator("gpt_auto_init_group", always=True, pre=True) 206 | def check_gpt_auto_init_group(cls,v): 207 | if isinstance(v,bool): 208 | if v: 209 | logger.success("已开启 gpt_auto_init_group 入群默认初始化人设") 210 | else: 211 | logger.success("已关闭 gpt_auto_init_group 入群默认初始化人设") 212 | return v 213 | 214 | @validator("gpt_auto_init_friend", always=True, pre=True) 215 | def check_gpt_auto_init_friend(cls,v): 216 | if isinstance(v,bool): 217 | if v: 218 | logger.success("已开启 gpt_auto_init_friend 好友默认初始化人设") 219 | else: 220 | logger.success("已关闭 gpt_auto_init_friend 好友默认初始化人设") 221 | return v 222 | 223 | @validator("gpt_init_group_pernal_name") 224 | def check_gpt_init_group_pernal_name(cls,v): 225 | if isinstance(v,str): 226 | logger.success(f"已应用 gpt_init_group_pernal_name 入群初始化默认人设名:{v}") 227 | return v 228 | 229 | @validator("gpt_init_friend_pernal_name") 230 | def check_gpt_init_friend_pernal_name(cls,v): 231 | if isinstance(v,str): 232 | logger.success(f"已应用 gpt_init_friend_pernal_name 好友初始化默认人设名:{v}") 233 | return v 234 | 235 | @validator("gpt_save_screen", always=True, pre=True) 236 | def check_gpt_save_screen(cls,v): 237 | if isinstance(v,bool): 238 | if v: 239 | logger.success("已开启 gpt_save_screen 消息与刷新错误截图保存") 240 | else: 241 | logger.success("已关闭 gpt_save_screen 消息与刷新错误截图保存") 242 | return v 243 | 244 | @validator("gpt_headless", always=True, pre=True) 245 | def check_gpt_headless(cls,v): 246 | if isinstance(v,bool): 247 | if v: 248 | logger.success("已开启 gpt_headless 模式") 249 | else: 250 | logger.success("已关闭 gpt_headless 模式") 251 | return v 252 | 253 | 254 | @validator("gpt_local_js", always=True, pre=True) 255 | def check_gpt_local_js(cls,v): 256 | if isinstance(v,bool): 257 | if v: 258 | logger.success("已开启 gpt_local_js 加载本地js") 259 | else: 260 | logger.success("已开启 gpt_local_js 联网获取js") 261 | return v 262 | 263 | 264 | @validator("gpt_free_image", always=True, pre=True) 265 | def check_gpt_free_image(cls,v): 266 | if isinstance(v,bool): 267 | if v: 268 | logger.success("已开启 gpt_free_image 免费账户上传图片,额度很低请注意") 269 | else: 270 | logger.success("已关闭 gpt_free_image 免费账户上传图片") 271 | return v 272 | 273 | 274 | @validator("gpt_force_upgrade_model", always=True, pre=True) 275 | def check_force_upgrade_model(cls,v): 276 | if isinstance(v,bool): 277 | if v: 278 | logger.success("已开启 gpt_force_upgrade_model 强制会话升级基础模型") 279 | else: 280 | logger.success("已关闭 gpt_force_upgrade_model 强制会话升级基础模型") 281 | return v 282 | 283 | config_gpt = get_plugin_config(Config) 284 | config_nb = get_driver().config -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | NoneBotPluginLogo 3 |
4 |

NoneBotPluginText

5 |
6 | 7 |
8 | 9 | # nonebot-plugin-gpt 10 | 11 | _✨ NoneBot GPT ✨_ 12 | 13 | 14 | 15 | license 16 | 17 | 18 | pypi 19 | 20 | python 21 | 22 |
23 | 24 | 25 | 26 | ## 📖 介绍 27 | 28 | 自用的使用浏览器ChatGPT接入Nonebot2,兼容 onebot v11 与 qq 适配器 29 | 30 | ### 使用条件 31 | 32 | 需要纯净ip用来过cf,另外根据使用账号数量需要相应多的内存 33 | 34 | ## 💿 安装 35 | 36 |
37 | 使用 nb-cli 安装 38 | 在 nonebot2 项目的根目录下打开命令行, 输入以下指令即可安装 39 | 40 | nb plugin install nonebot-plugin-gpt 41 | 42 |
43 | 44 |
45 | 使用包管理器安装 46 | 在 nonebot2 项目的插件目录下, 打开命令行, 根据你使用的包管理器, 输入相应的安装命令 47 | 48 |
49 | pip 50 | 51 | pip install nonebot-plugin-gpt 52 |
53 |
54 | pdm 55 | 56 | pdm add nonebot-plugin-gpt 57 |
58 |
59 | poetry 60 | 61 | poetry add nonebot-plugin-gpt 62 |
63 |
64 | conda 65 | 66 | conda install nonebot-plugin-gpt 67 |
68 | 69 | 打开 nonebot2 项目根目录下的 `pyproject.toml` 文件, 在 `[tool.nonebot]` 部分追加写入 70 | 71 | plugins = ["nonebot_plugin_gpt"] 72 | 73 |
74 | 75 |
76 | 升级插件版本 77 | 78 | pip install nonebot-plugin-gpt -U 79 | 80 |
81 | 82 | ## ⚙️ 配置 83 | 84 | 在 nonebot2 项目的`.env`文件中添加下表中的必填配置 85 | 86 | | 配置项 | 必填 | 默认值 | 类型 | 说明 | 87 | |:-----:|:----:|:----:|:----:|:----:| 88 | | gpt_session | 是 | 无 | List[Dict[str,str]] | openai账号密码 | 89 | | gpt_proxy | 否 | 无 | str | 使用的代理 | 90 | | arkose_status | 否 | false | bool | gpt是否开启了arkose验证 | 91 | | group_chat | 否 | true | bool | 群里开启多人识别 | 92 | | gpt_chat_start | 否 | [] | list | 聊天前缀,参考nb命令前缀 | 93 | | gpt_chat_start_in_msg | 否 | false | bool | 命令前缀是否包含在消息内 | 94 | | begin_sleep_time | 否 | false | bool | 关闭启动等待时间(建议账号数量大于5开启) | 95 | | gpt_chat_priority | 否 | 90 | int | gpt聊天响应优先级 | 96 | | gpt_command_priority | 否 | 19 | int | gpt命令响应优先级 | 97 | | gpt_white_list_mode | 否 | true | bool | 聊天白名单模式 | 98 | | gptplus_white_list_mode | 否 | true | bool | gptplus聊天白名单模式 | 99 | | gpt_replay_to_replay | 否 | false | bool | 是否响应"回复消息" | 100 | | gpt_ban_str | 否 | 无 | List[str] | 黑名单屏蔽词列表 | 101 | | gpt_manage_ids | 否 | 无 | List[str] | 超管群/频道id,通过日志等方式获得 | 102 | | gpt_lgr_markdown| 否 | false | bool | 以拉格兰md消息回复 | 103 | | gpt_httpx| 否 | false | bool | 使用httpx | 104 | | gpt_url_replace| 否 | false | bool | QQ适配器url输出时替换 | 105 | | gpt_auto_init_group| 否 | false | bool | 入群自动初始化人设 | 106 | | gpt_auto_init_friend| 否 | false | bool | 加好友后自动初始化人设 | 107 | | gpt_init_group_pernal_name| 否 | false | bool | 入群自动初始化的人设名 | 108 | | gpt_init_friend_pernal_name| 否 | false | bool | 加好友自动初始化的人设名 | 109 | | gpt_save_screen| 否 | false | bool | 自动保存非必须的错误截图 | 110 | | gpt_headless| 否 | true | bool | 使用无头浏览器 | 111 | | gpt_local_js| 否 | false | bool | 使用本地js不联网获取 | 112 | | gpt_free_image| 否 | false | bool | 免费账户使用图像识别(大概每天5次额度) | 113 | | gpt_force_upgrade_model| 否 | true | bool | 强制升级基础模型 | 114 | 115 | ```bash 116 | # gpt配置示例 117 | # 当mode为空或者为openai时,建议提前手动登录一次获取session_token填入(成功使用后可删除session_token项),mode目前不支持苹果账号 118 | gpt_session='[ 119 | { 120 | "email": "xxxx@hotmail.com", 121 | "password": "xxxx", 122 | "session_token": "ey....", 123 | }, 124 | { 125 | "email": "aaaa@gmail.com", 126 | "password": "xxxx", 127 | "mode": "google", 128 | }, 129 | { 130 | "email": "bbb@sss.com", 131 | "password": "xxxx", 132 | "mode": "microsoft", 133 | "help_email": "xxx@xx.com", 134 | "gptplus": True, 135 | }, 136 | ]' 137 | 138 | gpt_proxy='http://127.0.0.1:8080' 139 | # gpt_proxy='http://username:password@127.0.0.1:8080' 140 | 141 | arkose_status=false 142 | 143 | group_chat=true 144 | 145 | gpt_chat_start=[] 146 | 147 | gpt_chat_start_in_msg=false 148 | 149 | begin_sleep_time=true 150 | 151 | gpt_chat_priority=90 152 | 153 | gpt_command_priority=19 154 | 155 | gpt_white_list_mode=true 156 | 157 | gpt_replay_to_replay=false 158 | 159 | gpt_ban_str='[ 160 | "我是猪", 161 | "你是猪", 162 | ]' 163 | # qq适配器使用的超管群id 164 | gpt_manage_ids=['qq group id......'] 165 | # onebot适配器 拉格兰md消息兼容 166 | gpt_lgr_markdown=false 167 | # 使用httpx(暂不完善,请关闭) 168 | gpt_httpx=false 169 | # 开启QQ适配器url替换 170 | gpt_url_replace=true 171 | 172 | # 入群是否自动初始化人设 173 | gpt_auto_init_group=false 174 | gpt_init_group_pernal_name="猪" # 仅当上一条为true时生效 175 | # 加好友是否自动初始化人设 176 | gpt_auto_init_friend=false 177 | gpt_init_friend_pernal_name="私人猪" # 仅当上一条为true时生效 178 | 179 | # 发送消息异常和刷新cookie异常截图保存(登录失败截图固定开启,截图保存在bot目录screen下) 180 | gpt_save_screen=false 181 | 182 | # 使用无头浏览器 183 | gpt_headless=true 184 | 185 | # 使用本地js 186 | gpt_local_js=false 187 | 188 | # 开启免费账户图片识别(大概每天5额度) 189 | gpt_free_image=false 190 | 191 | # 强制升级基础模型,如4o-mini升级到4-1-mini 192 | gpt_force_upgrade_model=true 193 | 194 | 195 | # 插件需要一些其他的Nonebot基础配置,请检查是否存在 196 | # 机器人名 197 | nickname=["bot name"] 198 | # 超管QQ(onebot用) 199 | SUPERUSERS=["qq num"] 200 | 201 | ``` 202 | 203 | ## 🎉 使用 204 | ### 指令表 205 | | 指令 | 适配器 | 权限 | 需要@ | 范围 | 说明 | 206 | |:-----:|:----:|:----:|:----:|:----:|:----:| 207 | | @bot 聊天内容... | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | @或者叫名+内容 开始聊天,随所有者白名单模式设置改变 | 208 | | 初始化 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | 初始化(人设名) | 209 | | plus初始化 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | plus初始化(人设名) 会使用plus账户新开会话,可切换plus模型 | 210 | | 重置 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | 回到初始化人设后的第二句话时 | 211 | | 重置上一句 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | 刷新上一句的回答 | 212 | | 回到过去 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | 回到过去 <对话序号/p_id/最后一次出现的关键词> ,回到括号内的对话时间点| 213 | | 人设列表 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | 查看可用人设列表 | 214 | | 查看人设 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | 查看人设的具体内容 | 215 | | 添加人设 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | 添加人设 (人设名) | 216 | | 历史聊天 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | 查看当前人格历史聊天记录,可通过 - 或 : 限定搜索范围,如 2-4| 217 | | 历史聊天树 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | 查看当前人格历史聊天记录树状图| 218 | | 历史会话 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | 查看当前群聊私聊的会话列表,上限30 | 219 | | 切换会话 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | 切换会话 序列号,根据会话列表序号切换会话 | 220 | | md状态开启 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | 用户自开启markdown输出内容 | 221 | | md状态关闭 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | 用户自关闭markdown输出内容 | 222 | | 删除人设 | 兼容 | 超级管理员/超管群 | 是 | 群聊/私聊/频道 | 删除人设 (人设名) | 223 | | 黑名单列表 | 兼容 | 超级管理员/超管群 | 是 | 群聊/私聊/频道 | 查看黑名单列表 | 224 | | 解黑 | 兼容 | 超级管理员/超管群 | 是 | 群聊/私聊/频道 | 解黑<账号> ,解除黑名单 | 225 | | 白名单列表 | 兼容 | 超级管理员/超管群 | 是 | 群聊/私聊/频道 | 查看白名单列表 | 226 | | 工作状态 | 兼容 | 超级管理员/超管群 | 是 | 群聊/私聊/频道 | 查看当前所有账号的工作状态 | 227 | | 添加plus | 兼容 | 超级管理员/超管群 | 是 | 群聊/私聊/频道 | 添加plus 群号/账号/QQ适配器openid | 228 | | 删除plus | 兼容 | 超级管理员/超管群 | 是 | 群聊/私聊/频道 | 删除plus 群号/账号/QQ适配器openid | 229 | | plus切换 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | plus切换 <模型名称> ,如 4om/3.5/4/4o,白名单状态开启后,仅支持有plus状态的| 230 | | 全局plus | 兼容 | 超级管理员/超管群 | 是 | 群聊/私聊/频道 | 全局plus 开启/关闭,关闭后所有人的plus状态不可用,仅能使用3.5模型,超管自己除外 | 231 | | 删除白名单 | 兼容 | 超级管理员/超管群 | 是 | 群聊/私聊/频道 | 删除白名单 <账号/群号> (个人/群) ,删除白名单,最后不写默认为群 | 232 | | 添加白名单 | OneBot | 超级管理员/超管群 | 是 | 群聊/私聊 | 添加白名单(plus) <账号/群号> (个人/群) ,添加白名单,最后不写默认为群,加了plus字样则默认同时添加进plus状态 | 233 | | 获取本地id | qq | 无/白名单 | 是 | 群聊/频道 | 群聊内获取id | 234 | | 生成cdk | qq | 超管群 | 是 | 群聊/频道 | 生成cdk <群号/其他信息>,以绑定信息方式生成白名单cdk | 235 | | 出现吧 | qq | 无 | 是 | 群聊/频道 | 出现吧 \,以绑定id形式使用cdk加入白名单 | 236 | | 结束吧 | qq | 白名单 | 是 | 群聊/频道 | 结束吧 ,用户自主解除白名单 | 237 | 238 | > <为必填内容>,(为选填内容) 239 | 240 | > QQ适配器若添加plus状态,只能对方使用了cdk后,超管自己查看白名单列表,再手打openid到`添加plus`指令了,稍微有点麻烦,也许未来会优化 241 | 242 | > 不同模型的为独立会话,会分开保存,切换plus状态后会自动续接对应的会话 243 | 244 | 245 | ## 常见问题 246 | ### cloudflare验证 247 | 请先更换更干净的代理。cf验证问题,在无cfcookie的第一次登陆时一般会出现,可以在有窗口桌面的操作系统上,填写并运行以下脚本,手动过一次cf, 248 | 等待 data/chat_history/conversation/sessions 目录下有对应的session文件生成,将sessions文件夹复制到下方 `数据缓存` 里介绍的数据目录下 249 | ```python 250 | import asyncio 251 | import aioconsole 252 | from ChatGPTWeb import chatgpt 253 | from ChatGPTWeb.config import Personality, MsgData,IOFile 254 | 255 | # 此处填写要使用的账号信息 256 | session_token = [ 257 | { 258 | "email": "xxxx@hotmail.com", 259 | "password": "xxxx", 260 | "session_token": "ey....", 261 | }, 262 | { 263 | "email": "aaaa@gmail.com", 264 | "password": "xxxx", 265 | "mode": "google", 266 | }, 267 | { 268 | "email": "bbb@sss.com", 269 | "password": "xxxx", 270 | "mode": "microsoft", 271 | "help_email": "xxx@xx.com", 272 | "gptplus": True, 273 | }, 274 | ] 275 | personality_definition = Personality( 276 | [ 277 | { 278 | "name": "Programmer", 279 | 'value': 'You are python Programmer' 280 | }, 281 | ] 282 | ) 283 | 284 | chat = chatgpt(sessions=session_token, begin_sleep_time=False, headless=False,httpx_status=False,logger_level="DEBUG",stdout_flush=True,local_js=True) 285 | # 此处headless=False,通过关闭无头模式,来手动点击获取cf cookie 286 | 287 | async def main(): 288 | await asyncio.sleep(1000) 289 | 290 | loop = asyncio.get_event_loop() 291 | loop.run_until_complete(main()) 292 | 293 | ``` 294 | 295 | 296 | ### 浏览器问题 297 | 使用了新方案,如果浏览器出现问题,请尝试`playwright_firefox install firefox` 298 | 299 | ### 微软辅助邮箱验证 300 | 当触发验证后,会在启动目录生成带有启动账号名称的txt文件,键入收到的验证码并保存,即可自动验证。留意日志输出提示 301 | 302 | ### openai邮箱验证码 303 | 同微软邮箱。当触发验证后,会在启动目录生成带有启动账号名称与openai字样的txt文件,键入收到的验证码并保存,即可自动验证。留意日志输出提示 304 | 305 | ### 谷歌登录方式 306 | 请先从你的浏览器手动使用google登录chatgpt一次,然后访问`https://myaccount.google.com/`,使用浏览器插件Cookie-Editor导出该页面的Cookie为json格式。 当"\{email_address\}_google_cookie.txt"文件出现时,将复制的json粘贴进去并保存。 307 | 308 | ### markdown发送问题 309 | 协议bot的md似了,QQbot的md模板差不多也似了,如果你是QQBot原生md用户可以催我适配一下,不然这个功能就鸽了 310 | 311 | ### 历史聊天问题 312 | 历史聊天因为太多了,合并消息十有八九发不出来。现在新增了发不出来时转合并图片发送,但也可能发不出来。建议使用新功能,限定搜索,例如搜索2-5个会话, 313 | 可使用`2-5`或`:5`。初始化的对话默认为序列号1,现在不可查看。 314 | 315 | ### 合并消息图片异常 316 | 使用llonebot可能导致发出的合并消息的图片,在旧版pcqq上无法显示,临时解决方法是,手动转发该消息一次 317 | 318 | 319 | ### 数据缓存 320 | 见 nonebot_plugin_localstore 插件说明,通常为用户目录下 321 | ```bash 322 | # linux 323 | ~/.local/share/nonebot2/nonebot_plugin_gpt/\{bot_name\} 324 | ``` 325 | ```bash 326 | # windows 327 | C:\Users\UserName\AppData\Local\nonebot2\nonebot_plugin_gpt\\{bot_name\} 328 | ``` 329 | 330 | ### 自动初始化人设 331 | > 由于时间关系,仅测试了onebot适配器的群聊效果,onebot适配器私聊和QQ适配器群私聊理论上也支持,若有bug可发issue通知我改下; 332 | 333 | > 还是由于时间关系,暂时没写与白名单相关适配,自动初始化人设若开启,优先级会比白名单高,例如非白名单群,入群也会在gpt账户上创建一个会话(3.5的会话),当然没白名单该群后续触发不了这个会话 334 | 335 | ### 更新日志 336 | 2025.08.11 1.0.3 337 | 1. 修复cf问题 338 | 2. 修复openai和microsoft登录问题 339 | 3. google暂时无法登录,等待后续修复 340 | 4. openai可能也无法登录 341 | 342 | 343 | 2025.07.27 1.0.2 344 | 1. 修复windows下无法使用的问题 345 | 2. 修复onebot适配器bug导致发不出合并消息 346 | 3. 优化带有元数据的markdown消息展示 347 | 348 | 349 | 2025.07.20 1.0.1 350 | 1. 升级httpx版本至0.28.1,修复其参数 351 | 2. 优化底层,增强可用性,如果有问题请尝试`playwright_firefox install firefox` 352 | 3. 增加了历史会话列表 353 | 4. 增加了切换历史会话功能 354 | 5. 默认会开启联网搜索,下版本增加独立会话开关 355 | 6. 修复联网搜索导致的消息不完整 356 | 7. 增加了gpt生成和搜索到图片的获取展示 357 | 8. 更新模型列表与官网一致,增加了强制升级基础模型功能(gpt-4-1-mini) 358 | 9. 增加了历史聊天树 359 | 10. 增加历史聊天转图片问题,详见上方说明 360 | 11. 更改了plus相关逻辑,现在切换模型不会切换会话,但只有被标记为使用plus账号的会话,才能切换模型 361 | 12. 轻微改变`工作状态`显示 362 | 363 | 364 | 2025.02.09 0.0.43 365 | 1. 添加openai登录验证码填写功能 366 | 2. 修复微软账户登录步骤 367 | 3. 修复消息有时接收处理错误的问题 368 | 369 | 370 | 2024.12.12 0.0.42 371 | 1. 更新可用性 372 | 2. 调整黑名单列表为100条一张图,多图发送 373 | 374 | 375 | 2024.12.01 0.0.40 376 | 1. 修复插件无法使用的问题 377 | 2. 优化工作状态查看,增加白名单状态 378 | 3. 添加发送消息异常和刷新cookie异常截图保存(登录失败截图固定开启,截图保存在bot目录screen下) 379 | 4. readme添加cf验证操作步骤说明 380 | 381 | 382 | 2024.07.28 0.0.39 383 | 1. 添加使用plus模型时,可上传文件(目前只支持图片) 384 | 2. 继续尝试修复长时间运行时,access_token过期未自动刷新的问题 385 | 386 | 387 | 2024.07.21 0.0.37 388 | 1. 添加并修改默认使用模型喂gpt-4o-mini(3.5仍然可用但性能下降很多)(4om和3.5免费用户都可用,但3.5预计迟早下架,所以不建议使用,也就偷个懒,不添加非plus用户切换3.5功能了) 389 | 2. 更新openai接口 390 | 391 | 392 | 2024.07.16 0.0.36 393 | 1. 修复0.0.35版本中,未正确捕获自身入群事件的问题 394 | 2. 自动初始化人设添加频道相关支持 395 | 3. 尝试修复长时间运行时,access_token过期未自动刷新的问题 396 | 397 | 398 | 2024.07.15 0.0.35 399 | 1. 修复0.0.34造成的gpt plus账户会话失败的问题 400 | 2. 优化添加人设名称识别 401 | 3. 添加新功能,入群/加好友后,自动初始化人设,让bot一个人出门在外也更加顺畅 402 | 403 | 404 | 2024.07.12 0.0.34 405 | 1. 修复部分消息接收失败问题 406 | 407 | 408 | 2024.07.11 0.0.33 409 | 1. 添加QQ适配器 Url 输出替换 410 | 2. 优化登录流程 411 | 3. 优化消息超时问题 412 | 4. 添加代理用的用户名密码 413 | 414 | 415 | 2024.06.23 0.0.32 416 | 1. 修复多账户下相关命令换号发送的情况 417 | 2. 优化了登录部分 418 | 3. 修复上次更新导致的一个bug,让私聊用户丢失了原有的会话,本次更新后原有私聊用户会话会切换回去,在两次更新期间的新用户会话会丢失(偷个懒,就不做迁移了) 419 | 420 | 421 | 2024.06.15 0.0.31 422 | 1. 优化登录方式 423 | 2. 优化google登录缓存 424 | 3. 优化白名单列表,新增部分plus白名单单独显示,提示两种白名单模式独立运作 425 | 426 | 427 | 2024.06.11 0.0.29 428 | 1. 修复openai新cookie跨域问题 429 | 2. 修复google登录问题 430 | 3. 优化了token和状态显示 431 | 432 | 433 | 2024.06.04 0.0.28 434 | 1. 添加gptplus账户支持及其gpt4 4o模型使用 435 | 2. 修复windows下数据目录异常问题 436 | 3. 添加QQ适配器图片发送支持 437 | 4. 优化图片间距 438 | 5. 修复添加账户后,会话数计数错误 439 | 440 | 441 | 2024.05.20 0.0.26 442 | 1. 修复非全局代理下,websocket灰度账号代理未生效的问题 443 | 444 | 445 | 2024.05.16 0.0.25 446 | 1. 修复websocket账号未正常工作的bug 447 | 2. 跟进openai新(旧)token验证 448 | 3. 修正工作状态会话数标题错误 449 | 4. 为白名单列表添加cdk生成源信息,方便溯源 450 | 451 | 452 | 2024.05.10 0.0.24 453 | 1. 跟进新token生成验证 454 | 2. 为初始化人设异常时添加错误提示 455 | 456 | 457 | 2024.05.07 0.0.23 458 | 1. 修复webssocket url未更新 459 | 2. 优化工作状态输出会话数遮蔽问题 460 | 3. 修复空数据时未正确重试的问题 461 | 4. 兼容pyd2 462 | 463 | 464 | 2024.05.06 0.0.20 465 | 1. 优化登录和消息接收流程 466 | 2. 优化初始化时多bot账号主体发送消息不对的问题 467 | 3. 兼容新websocket接收方式(我以为都SSE了) 468 | 469 | 470 | 2024.05.04 0.0.18 471 | 1. 跟进openai新搞得幺蛾子验证(加班太累了,更晚了) 472 | 2. markdown被人作没了,唉(吐槽) 473 | 3. 目前只简单实现了新验证,代码很乱,抽空应该会优化 474 | 475 | 476 | 2024.04.18 0.0.17 477 | 1. 跟进新markdown发送方式 478 | 479 | 480 | 2024.04.17 0.0.15 481 | 1. 尝试解决持久连接接收不到消息的问题 482 | 2. 添加markdown消息用户自定义开关(QQ适配器md能力待支持) 483 | 3. 优化markdown消息发送时,人设名未匹配消除的问题 484 | 485 | 486 | 2024.03.24 0.0.13 487 | 1. 修复qq适配器的人设列表无法显示的问题 488 | 2. 添加了会话超时时间,避免意外情况导致session阻塞 489 | 3. 优化了工作状态显示,目前login为登录中,登陆后未工作则为ready 490 | 4. 添加了全cookie保存,降低重新登录异常的风险 491 | 492 | 493 | 2024.03.22 0.0.12 494 | 1. 临时修复了一些错误 495 | 2. 优化多账户私聊混乱问题 496 | 497 | 498 | 2024.03.20 499 | 1. 没有新功能增加,临时更新一下添加httpx关闭配置(现默认关闭),目前它还有些问题。新流程还没写完,等下次放假。 500 | 501 | 502 | 2024.03.17 503 | 1. 优化了底层代码,减少错误,暂不支持gpt plus账号(待填坑) 504 | 2. 支持拉格兰md发送 505 | 506 | 507 | 2024.03.13 508 | 1. 兼容拉格兰合并转发,修复合并转发失败的问题 509 | 2. 添加自定义聊天前缀,现在可以不用@也能触发了 510 | 511 | 512 | 2024.03.11 513 | 1. 临时修复200问题(chatgpt新的websocket问题),最近好忙,等闲了的时候再优化,有什么问题都可以先提issue 514 | 515 | 516 | 2024.02.19 517 | 1. 临时修复200问题 与 添加 微软辅助邮箱验证 518 | 519 | ## 待续 520 | 自用挺久了,匆忙改改发出来,很多东西还没补充 521 | -------------------------------------------------------------------------------- /nonebot_plugin_gpt/__init__.py: -------------------------------------------------------------------------------- 1 | from ChatGPTWeb import chatgpt 2 | from ChatGPTWeb.config import Personality 3 | from nonebot.log import logger 4 | from nonebot import on_command,on_message,on_notice 5 | from nonebot.adapters.onebot.v11 import Message,MessageEvent,GroupIncreaseNoticeEvent,FriendAddNoticeEvent,Bot 6 | from nonebot.adapters.qq.event import MessageEvent as QQMessageEvent,GroupAddRobotEvent,FriendAddEvent,GuildMemberUpdateEvent 7 | from nonebot.adapters.qq.message import Message as QQMessage 8 | from nonebot.adapters.qq import Bot as QQBot 9 | from nonebot.matcher import Matcher,current_bot 10 | from nonebot.params import Arg, CommandArg,EventMessage 11 | from nonebot.plugin import PluginMetadata 12 | from nonebot.typing import T_State 13 | from nonebot import get_driver 14 | from importlib.metadata import version 15 | import asyncio 16 | 17 | 18 | from .config import config_gpt,Config 19 | from .source import data_dir 20 | from .check import gpt_manage_rule,gpt_rule,plus_status 21 | 22 | from .api import ( 23 | add_default_ps, 24 | chat_msg, 25 | reset_history, 26 | back_last, 27 | back_anywhere, 28 | init_gpt, 29 | ps_list, 30 | cat_ps, 31 | add_ps1, 32 | add_ps2, 33 | add_ps3, 34 | add_ps4, 35 | add_ps5, 36 | del_ps, 37 | chatmsg_history, 38 | status_pic, 39 | black_list, 40 | remove_ban_user, 41 | add_white_list, 42 | del_white_list, 43 | white_list, 44 | md_status, 45 | get_id_from_guild_group, 46 | random_cdk_api, 47 | add_checker_api, 48 | add_plus, 49 | del_plus, 50 | plus_change, 51 | plus_all_status, 52 | init_personal_api, 53 | chatmsg_history_tree, 54 | conversation_change, 55 | conversations_list 56 | 57 | ) 58 | 59 | try: 60 | __version__ = version("nonebot_plugin_gpt") 61 | except Exception: 62 | __version__ = None 63 | 64 | 65 | 66 | __plugin_meta__ = PluginMetadata( 67 | name="ChatGPT 聊天", 68 | description="通过浏览器使用 ChatGPT,兼容 onebot v11 与 adapter-qq 适配器", 69 | usage=""" 70 | | 指令 | 适配器 | 权限 | 需要@ | 范围 | 说明 | 71 | |:-----:|:----:|:----:|:----:|:----:|:----:| 72 | | @bot 聊天内容... | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | @或者叫名+内容 开始聊天,随所有者白名单模式设置改变 | 73 | | 初始化 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | 初始化(人设名) | 74 | | 重置 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | 回到初始化人设后的第二句话时 | 75 | | 重置上一句 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | 刷新上一句的回答 | 76 | | 回到过去 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | 回到过去 <对话序号/p_id/最后一次出现的关键词> ,回到括号内的对话时间点| 77 | | 人设列表 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | 查看可用人设列表 | 78 | | 查看人设 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | 查看人设的具体内容 | 79 | | 添加人设 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | 添加人设 (人设名) | 80 | | 历史聊天 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | 查看当前人格历史聊天记录 | 81 | | md状态开启 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | 用户自开启markdown输出内容 | 82 | | md状态关闭 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | 用户自关闭markdown输出内容 | 83 | | 删除人设 | 兼容 | 超级管理员/超管群 | 是 | 群聊/私聊/频道 | 删除人设 (人设名) | 84 | | 黑名单列表 | 兼容 | 超级管理员/超管群 | 是 | 群聊/私聊/频道 | 查看黑名单列表 | 85 | | 解黑 | 兼容 | 超级管理员/超管群 | 是 | 群聊/私聊/频道 | 解黑<账号> ,解除黑名单 | 86 | | 白名单列表 | 兼容 | 超级管理员/超管群 | 是 | 群聊/私聊/频道 | 查看白名单列表 | 87 | | 工作状态 | 兼容 | 超级管理员/超管群 | 是 | 群聊/私聊/频道 | 查看当前所有账号的工作状态 | 88 | | 添加plus | 兼容 | 超级管理员/超管群 | 是 | 群聊/私聊/频道 | 添加plus 群号/账号/QQ适配器openid | 89 | | 删除plus | 兼容 | 超级管理员/超管群 | 是 | 群聊/私聊/频道 | 删除plus 群号/账号/QQ适配器openid | 90 | | plus切换 | 兼容 | 无/白名单 | 是 | 群聊/私聊/频道 | plus切换 <模型名称> ,如 3.5/4/4o,白名单状态开启后,仅支持有plus状态的| 91 | | 全局plus | 兼容 | 超级管理员/超管群 | 是 | 群聊/私聊/频道 | 全局plus 开启/关闭,关闭后所有人的plus状态不可用,仅能使用3.5模型,超管自己除外 | 92 | | 删除白名单 | 兼容 | 超级管理员/超管群 | 是 | 群聊/私聊/频道 | 删除白名单 <账号/群号> (个人/群) ,删除白名单,最后不写默认为群 | 93 | | 添加白名单 | OneBot | 超级管理员/超管群 | 是 | 群聊/私聊 | 添加白名单(plus) <账号/群号> (个人/群) ,添加白名单,最后不写默认为群,加了plus字样则默认同时添加进plus状态 | 94 | | 获取本地id | qq | 无/白名单 | 是 | 群聊/频道 | 群聊内获取id | 95 | | 生成cdk | qq | 超管群 | 是 | 群聊/频道 | 生成cdk <群号/其他信息>,以绑定信息方式生成白名单cdk | 96 | | 出现吧 | qq | 无 | 是 | 群聊/频道 | 出现吧 ,以绑定id形式使用cdk加入白名单 | 97 | | 结束吧 | qq | 白名单 | 是 | 群聊/频道 | 结束吧 ,用户自主解除白名单 | 98 | """, 99 | type="application", 100 | config=Config, 101 | homepage="https://github.com/nek0us/nonebot-plugin-gpt", 102 | supported_adapters={"~onebot.v11","~qq"}, 103 | extra={ 104 | "author":"nek0us", 105 | "version":__version__, 106 | } 107 | ) 108 | 109 | if isinstance(config_gpt.gpt_session,list): 110 | personality = Personality([],data_dir) 111 | 112 | chatbot = chatgpt( 113 | sessions = config_gpt.gpt_session, 114 | plugin = True, 115 | arkose_status = config_gpt.arkose_status, 116 | chat_file = data_dir, 117 | proxy = config_gpt.gpt_proxy, 118 | begin_sleep_time = config_gpt.begin_sleep_time, 119 | personality=personality, 120 | httpx_status=config_gpt.gpt_httpx, 121 | save_screen=config_gpt.gpt_save_screen, 122 | headless=config_gpt.gpt_headless, 123 | local_js=config_gpt.gpt_local_js, 124 | ) 125 | 126 | driver = get_driver() 127 | @driver.on_startup 128 | async def d(): 129 | logger.info("登录GPT账号中") 130 | loop = asyncio.get_event_loop() 131 | asyncio.run_coroutine_threadsafe(chatbot.__start__(loop),loop) 132 | await add_default_ps(chatbot) 133 | 134 | chat = on_message(priority=config_gpt.gpt_chat_priority,rule=gpt_rule) 135 | @chat.handle() 136 | async def chat_handle(bot: Bot,event: MessageEvent|QQMessageEvent,text:Message|QQMessage = EventMessage()): 137 | await chat_msg(bot,event,chatbot,text) 138 | 139 | 140 | reset = on_command("reset",aliases={"重置记忆","重置","重置对话"},rule=gpt_rule,priority=config_gpt.gpt_command_priority,block=True) 141 | @reset.handle() 142 | async def reset_handle(event: MessageEvent|QQMessageEvent,text:Message|QQMessage = EventMessage()): 143 | await reset_history(event,chatbot,text) 144 | 145 | 146 | last = on_command("backlast",aliases={"重置上一句","重置上句"},rule=gpt_rule,priority=config_gpt.gpt_command_priority,block=True) 147 | @last.handle() 148 | async def last_handle(event: MessageEvent|QQMessageEvent,text:Message|QQMessage = EventMessage()): 149 | await back_last(event,chatbot,text) 150 | 151 | 152 | back = on_command("backloop",aliases={"回到过去"},rule=gpt_rule,priority=config_gpt.gpt_command_priority,block=True) 153 | @back.handle() 154 | async def back_handle(event: MessageEvent|QQMessageEvent,arg: Message|QQMessage = CommandArg()): 155 | await back_anywhere(event,chatbot,arg) 156 | 157 | 158 | init = on_command("init",aliases={"初始化","初始化人格","加载人格","加载预设"},rule=gpt_rule,priority=config_gpt.gpt_command_priority,block=True) 159 | @init.handle() 160 | async def init_handle(event: MessageEvent|QQMessageEvent,arg :Message|QQMessage = CommandArg()): 161 | await init_gpt(event,chatbot,arg) 162 | 163 | plus_init = on_command("plus_init",aliases={"plus初始化","plus初始化人格","plus加载人格","plus加载预设"},rule=gpt_rule,priority=config_gpt.gpt_command_priority,block=True) 164 | @plus_init.handle() 165 | async def plus_init_handle(event: MessageEvent|QQMessageEvent,arg :Message|QQMessage = CommandArg()): 166 | await init_gpt(event,chatbot,arg,True) 167 | 168 | personality_list = on_command("人设列表",aliases={"预设列表","人格列表"},rule=gpt_rule,priority=config_gpt.gpt_command_priority,block=True) 169 | @personality_list.handle() 170 | async def personality_list_handle(event: MessageEvent|QQMessageEvent): 171 | await ps_list(event,chatbot) 172 | 173 | 174 | cat_personality = on_command("查看人设",aliases={"查看预设","查看人格"},rule=gpt_rule,priority=config_gpt.gpt_command_priority,block=True) 175 | @cat_personality.handle() 176 | async def cat_personality_handle(event: MessageEvent|QQMessageEvent,arg: Message|QQMessage = CommandArg()): 177 | await cat_ps(event,chatbot,arg) 178 | 179 | 180 | add_personality = on_command("添加人设",aliases={"添加预设","添加人格"},rule=gpt_rule,priority=config_gpt.gpt_command_priority,block=True) 181 | @add_personality.handle() 182 | async def add_personality_handle(event: MessageEvent|QQMessageEvent,status: T_State,arg :Message|QQMessage = CommandArg()): 183 | await add_ps1(event,status,arg) 184 | 185 | @add_personality.got("name",prompt="人设名叫什么?") 186 | async def add_personality_handle2(status: T_State,name: Message|QQMessage = Arg()): 187 | await add_ps2(status,name) 188 | 189 | 190 | @add_personality.got("r18",prompt="是R18人设吗?(回答 是 / 否)") 191 | async def add_personality_handle3(status: T_State,r18: Message|QQMessage = Arg()): 192 | await add_ps3(status,r18) 193 | 194 | @add_personality.got("open",prompt="要公开给其他人也可用吗?(回答 公开 / 私有)") 195 | async def add_personality_handle4(status: T_State,open: Message|QQMessage = Arg()): 196 | await add_ps4(status,open) 197 | 198 | @add_personality.got("value",prompt="请发送人设内容") 199 | async def add_personality_handle5(status: T_State,value: Message|QQMessage = Arg()): 200 | await add_ps5(status,value,chatbot) 201 | 202 | del_personality = on_command("删除人设",aliases={"删除人格","删除人设"},rule=gpt_manage_rule,priority=config_gpt.gpt_command_priority,block=True) 203 | @del_personality.handle() 204 | async def del_personality_handle(event: MessageEvent|QQMessageEvent,arg :Message|QQMessage = CommandArg()): 205 | await del_ps(event,chatbot,arg) 206 | 207 | chat_history = on_command("history",aliases={"历史聊天","历史记录"},rule=gpt_rule,priority=config_gpt.gpt_command_priority,block=True) 208 | @chat_history.handle() 209 | async def chat_history_handle(bot:Bot,event: MessageEvent|QQMessageEvent,text:Message|QQMessage = CommandArg()): 210 | await chatmsg_history(bot,event,chatbot,text) 211 | 212 | chat_history = on_command("history_tree",aliases={"历史聊天树","历史记录树"},rule=gpt_rule,priority=config_gpt.gpt_command_priority,block=True) 213 | @chat_history.handle() 214 | async def chat_history_handle(event: MessageEvent|QQMessageEvent,text:Message|QQMessage = CommandArg()): 215 | await chatmsg_history_tree(event,chatbot,text) 216 | 217 | chat_conversations = on_command("conversations",aliases={"历史人设","历史会话"},rule=gpt_rule,priority=config_gpt.gpt_command_priority,block=True) 218 | @chat_conversations.handle() 219 | async def chat_conversations_handle(event: MessageEvent|QQMessageEvent): 220 | await conversations_list(chatbot,event) 221 | 222 | change_conversation = on_command("change_conversation",aliases={"切换会话"},rule=gpt_rule,priority=config_gpt.gpt_command_priority,block=True) 223 | @change_conversation.handle() 224 | async def change_conversation_handle(event: MessageEvent|QQMessageEvent,arg:Message|QQMessage = CommandArg()): 225 | await conversation_change(event,arg) 226 | 227 | status = on_command("gpt_status",aliases={"工作状态"},rule=gpt_manage_rule,priority=config_gpt.gpt_command_priority,block=True) 228 | @status.handle() 229 | async def status_handle(matcher: Matcher): 230 | await status_pic(matcher,chatbot) 231 | 232 | ban_list = on_command("黑名单列表",rule=gpt_manage_rule,priority=config_gpt.gpt_command_priority,block=True) 233 | @ban_list.handle() 234 | async def ban_list_handle(event: MessageEvent|QQMessageEvent,arg :Message|QQMessage = CommandArg()): 235 | await black_list(chatbot,event,arg) 236 | 237 | ban_del = on_command("解黑",rule=gpt_manage_rule,aliases={"解除黑名单","删除黑名单"},priority=config_gpt.gpt_command_priority,block=True) 238 | @ban_del.handle() 239 | async def ban_del_handle(arg: Message|QQMessage = CommandArg()): 240 | await remove_ban_user(arg) 241 | 242 | del_white_cmd = on_command("删除白名单",aliases={"解除白名单","解白"},rule=gpt_manage_rule,priority=config_gpt.gpt_command_priority,block=True) 243 | @del_white_cmd.handle() 244 | async def del_white_handle(arg: Message|QQMessage = CommandArg()): 245 | await del_white_list(arg) 246 | 247 | white_list_cmd = on_command("白名单列表",rule=gpt_manage_rule,priority=config_gpt.gpt_command_priority,block=True) 248 | @white_list_cmd.handle() 249 | async def white_list_handle(): 250 | await white_list(chatbot) 251 | 252 | md_status_cmd = on_command("md状态",rule=gpt_rule,priority=config_gpt.gpt_command_priority,block=True) 253 | @md_status_cmd.handle() 254 | async def md_status_cmd_handle(event: MessageEvent|QQMessageEvent,arg: Message|QQMessage = CommandArg()): 255 | await md_status(event,arg) 256 | 257 | add_plus_cmd = on_command("添加plus",rule=gpt_manage_rule,priority=config_gpt.gpt_command_priority,block=True) 258 | @add_plus_cmd.handle() 259 | async def add_plus_handle(arg: Message|QQMessage = CommandArg()): 260 | await add_plus(arg) 261 | 262 | del_plus_cmd = on_command("删除plus",rule=gpt_manage_rule,priority=config_gpt.gpt_command_priority,block=True) 263 | @del_plus_cmd.handle() 264 | async def del_plus_handle(arg: Message|QQMessage = CommandArg()): 265 | await del_plus(arg) 266 | 267 | plus_change_cmd = on_command("plus切换",rule=plus_status,priority=config_gpt.gpt_command_priority,block=True) 268 | @plus_change_cmd.handle() 269 | async def plus_change_handle(event: MessageEvent|QQMessageEvent,arg: Message|QQMessage = CommandArg()): 270 | await plus_change(event,arg) 271 | 272 | plus_all_status_cmd = on_command("全局plus",rule=gpt_manage_rule,priority=config_gpt.gpt_command_priority,block=True) 273 | @plus_all_status_cmd.handle() 274 | async def plus_all_status_handle(arg: Message|QQMessage = CommandArg()): 275 | await plus_all_status(arg) 276 | 277 | 278 | 279 | # ------------------------------ adapter-OneBot 280 | add_white_cmd = on_command("添加白名单",aliases={"加白"},rule=gpt_manage_rule,priority=config_gpt.gpt_command_priority,block=True) 281 | @add_white_cmd.handle() 282 | async def add_white_handle(arg: Message = CommandArg()): 283 | await add_white_list(arg) 284 | 285 | # ------------------------------ adapter-qq 286 | get_local_id = on_command("获取本地id",rule=gpt_rule,priority=config_gpt.gpt_command_priority,block=True) 287 | @get_local_id.handle() 288 | async def get_local_id_handle(event: QQMessageEvent,matcher: Matcher): 289 | id,value = await get_id_from_guild_group(event) 290 | await matcher.finish(id) 291 | 292 | random_cdk = on_command("生成cdk",rule=gpt_manage_rule,priority=config_gpt.gpt_command_priority,block=True) 293 | @random_cdk.handle() 294 | async def random_cdk_handle(arg:QQMessage = CommandArg()): 295 | await random_cdk_api(arg) 296 | 297 | add_checker = on_command("出现吧",priority=config_gpt.gpt_command_priority,block=True) 298 | @add_checker.handle() 299 | async def add_checker_handle(event: QQMessageEvent,arg: QQMessage = CommandArg()): 300 | await add_checker_api(event,arg) 301 | 302 | del_checker = on_command("结束吧",rule=gpt_rule,priority=config_gpt.gpt_command_priority,block=True) 303 | @del_checker.handle() 304 | async def del_checker_handle(event: QQMessageEvent): 305 | id,value = await get_id_from_guild_group(event) 306 | await del_white_list(id) 307 | 308 | init_personal = on_notice(block=False,priority=config_gpt.gpt_chat_priority) 309 | @init_personal.handle() 310 | async def init_personal_handle(event: GroupAddRobotEvent|FriendAddNoticeEvent|GroupIncreaseNoticeEvent|FriendAddEvent|GuildMemberUpdateEvent): 311 | if isinstance(event,GroupAddRobotEvent): 312 | # QQ群 313 | if config_gpt.gpt_auto_init_group: 314 | if not config_gpt.gpt_init_group_pernal_name: 315 | logger.warning(f"检测到已开启入群初始化人设,但未配置具体人设名,类型 GroupAddRobotEvent, id: {event.group_openid} 入群初始化人设失败") 316 | else: 317 | logger.info(f"检测到已开启入群初始化人设,类型 GroupAddRobotEvent, id: {event.group_openid} 即将入群初始化人设 {config_gpt.gpt_init_group_pernal_name}") 318 | await init_personal_api(chatbot,id=event.group_openid,personal_name=config_gpt.gpt_init_group_pernal_name,type_from='QQgroup') 319 | elif isinstance(event,FriendAddNoticeEvent): 320 | # onebot 好友 321 | if config_gpt.gpt_auto_init_friend: 322 | if not config_gpt.gpt_init_friend_pernal_name: 323 | logger.warning(f"检测到已开启好友初始化人设,但未配置具体人设名,类型 FriendAddNoticeEvent, id: {event.get_user_id()} 好友初始化人设失败") 324 | else: 325 | logger.info(f"检测到已开启好友初始化人设,类型 FriendAddNoticeEvent, id: {event.get_user_id()} 即将好友初始化人设 {config_gpt.gpt_init_friend_pernal_name}") 326 | await init_personal_api(chatbot,id=event.get_user_id(),personal_name=config_gpt.gpt_init_friend_pernal_name,type_from='private') 327 | elif isinstance(event,GroupIncreaseNoticeEvent): 328 | # onebot 群 329 | if event.get_user_id() == str(event.self_id): 330 | if config_gpt.gpt_auto_init_group: 331 | if not config_gpt.gpt_init_group_pernal_name: 332 | logger.warning(f"检测到已开启入群初始化人设,但未配置具体人设名,类型 GroupIncreaseNoticeEvent, id: {str(event.group_id)} 入群初始化人设失败") 333 | else: 334 | logger.info(f"检测到已开启入群初始化人设,类型 GroupIncreaseNoticeEvent, id: {str(event.group_id)} 即将入群初始化人设 {config_gpt.gpt_init_group_pernal_name}") 335 | await init_personal_api(chatbot,id=str(event.group_id),personal_name=config_gpt.gpt_init_group_pernal_name,type_from='group') 336 | elif isinstance(event,FriendAddEvent): 337 | # QQ好友 338 | if config_gpt.gpt_auto_init_friend: 339 | if not config_gpt.gpt_init_friend_pernal_name: 340 | logger.warning(f"检测到已开启好友初始化人设,但未配置具体人设名,类型 FriendAddEvent, id: {event.get_user_id()} 好友初始化人设失败") 341 | else: 342 | logger.info(f"检测到已开启好友初始化人设,类型 FriendAddEvent, id: {event.get_user_id()} 即将好友初始化人设 {config_gpt.gpt_init_friend_pernal_name}") 343 | await init_personal_api(chatbot,id=event.get_user_id(),personal_name=config_gpt.gpt_init_friend_pernal_name,type_from='QQprivate') 344 | elif isinstance(event,GuildMemberUpdateEvent): 345 | # QQ频道 346 | bot: QQBot = current_bot.get() # type: ignore 347 | if bot.self_info.id == event.op_user_id: 348 | if config_gpt.gpt_auto_init_group: 349 | if not config_gpt.gpt_init_group_pernal_name: 350 | logger.warning(f"检测到已开启频道初始化人设,但未配置具体人设名,类型 GuildMemberUpdateEvent, id: {event.guild_id} 频道初始化人设失败") 351 | else: 352 | logger.info(f"检测到已开启频道初始化人设,类型 GuildMemberUpdateEvent, id: {event.guild_id} 即将频道初始化人设 {config_gpt.gpt_init_group_pernal_name}") 353 | await init_personal_api(chatbot,id=event.guild_id,personal_name=config_gpt.gpt_init_group_pernal_name,type_from='QQguild') 354 | 355 | 356 | else: 357 | logger.warning("未检测到gpt账号信息,插件未成功加载") -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /nonebot_plugin_gpt/api.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from nonebot.adapters.onebot.v11 import Message,MessageSegment,MessageEvent,GroupMessageEvent,PrivateMessageEvent,Bot 4 | from nonebot.matcher import Matcher,current_matcher,current_event 5 | from nonebot.params import EventMessage 6 | from ChatGPTWeb import chatgpt 7 | from ChatGPTWeb.config import MsgData,IOFile,get_model_by_key,all_models_keys,all_models_values,all_free_models_values 8 | from nonebot.log import logger 9 | from nonebot.typing import T_State 10 | from nonebot import require 11 | from nonebot_plugin_sendmsg_by_bots import tools 12 | from httpx import AsyncClient 13 | from more_itertools import chunked 14 | from base64 import b64encode 15 | from typing import Literal 16 | from filetype import guess 17 | import json 18 | import re 19 | import uuid 20 | from datetime import datetime 21 | 22 | from .config import config_gpt,config_nb 23 | from .source import ( 24 | grouppath, 25 | group_conversations_path, 26 | privatepath, 27 | private_conversations_path, 28 | mdstatus, 29 | personpath, 30 | whitepath, 31 | cdklistpath, 32 | cdksource, 33 | ban_str_path, 34 | banpath, 35 | plusstatus, 36 | 37 | ) 38 | from .check import ( 39 | QQMessageEvent, 40 | QQMessage, 41 | QQGroupAtMessageCreateEvent, 42 | QQAtMessageCreateEvent, 43 | QQMessageSegment, 44 | get_id_from_guild_group, 45 | get_id_from_all, 46 | ban_check, 47 | add_ban, 48 | add_white, 49 | del_white, 50 | 51 | 52 | ) 53 | require("nonebot_plugin_htmlrender") 54 | from nonebot_plugin_htmlrender import md_to_pic # noqa: E402 55 | 56 | 57 | bot_name = list(config_nb.nickname) 58 | 59 | 60 | async def name_or_tome(event: MessageEvent) -> bool: 61 | ''' 62 | ## if name return True 63 | ## if tome return False''' 64 | if [x for x in event.original_message if x.type == "at" and x.data["qq"] == str(event.self_id)]: 65 | return False 66 | else: 67 | return True 68 | 69 | 70 | async def group_handle(data: MsgData,group_member: list) -> MsgData: 71 | qq_num_list = re.findall(r"[1-9][0-9]{4,10}",data.msg_recv) 72 | if qq_num_list: 73 | for x in group_member: 74 | for y in qq_num_list: 75 | if x["user_id"] == int(y): 76 | if x["card"]: 77 | if data.msg_raw: 78 | data.msg_raw = [msg.replace(y,x["card"]) for msg in data.msg_raw] 79 | data.msg_recv = data.msg_recv.replace(y,x["card"]) 80 | else: 81 | if data.msg_raw: 82 | data.msg_raw = [msg.replace(y,x["nickname"]) for msg in data.msg_raw] 83 | data.msg_recv = data.msg_recv.replace(y,x["nickname"]) 84 | data.msg_recv = data.msg_recv.replace("编号","") 85 | return data 86 | 87 | def replace_name(data: MsgData) -> MsgData: 88 | for name in bot_name: 89 | if data.msg_raw: 90 | data.msg_raw = [x.replace(f"{name}:","").replace(f"{name}:","") for x in data.msg_raw] 91 | data.msg_recv = data.msg_recv.replace(f"{name}:","").replace(f"{name}:","") 92 | return data 93 | 94 | def get_c_id(id:str, data: MsgData,c_type: Literal['group','private'] = 'group') -> MsgData: 95 | group_conversations = json.loads(group_conversations_path.read_text()) 96 | private_conversations = json.loads(private_conversations_path.read_text()) 97 | 98 | if c_type == 'group': 99 | tmp = json.loads(grouppath.read_text("utf-8")) 100 | if id in group_conversations: 101 | for c in group_conversations[id]: 102 | if tmp[id] == c["conversation_id"]: 103 | data.title = c["conversation_name"] 104 | else: 105 | tmp = json.loads(privatepath.read_text("utf-8")) 106 | if id in private_conversations: 107 | for c in private_conversations[id]: 108 | if tmp[id] == c["conversation_id"]: 109 | data.title = c["conversation_name"] 110 | 111 | 112 | # if data.gpt_model == "gpt-4": 113 | # if id + '-gpt-4' in tmp: 114 | # data.conversation_id = tmp[id + '-gpt-4'] 115 | # elif data.gpt_model == "gpt-4o": 116 | # if id + '-gpt-4o' in tmp: 117 | # data.conversation_id = tmp[id + '-gpt-4o'] 118 | # else: 119 | # if id in tmp: 120 | # data.conversation_id = tmp[id] 121 | 122 | # 不再根据模型隔离会话,同一个会话内也可以切换模型,兼容官网改动 123 | if id in tmp: 124 | data.conversation_id = tmp[id] 125 | 126 | return data 127 | 128 | def set_c_id(id:str, data: MsgData,c_type: Literal['group','private'] = 'group'): 129 | group_conversations = json.loads(group_conversations_path.read_text()) 130 | private_conversations = json.loads(private_conversations_path.read_text()) 131 | if c_type == 'group': 132 | tmp = json.loads(grouppath.read_text("utf-8")) 133 | if id in group_conversations: 134 | for c in group_conversations[id]: 135 | if data.conversation_id == c["conversation_id"]: 136 | if data.title: 137 | c["conversation_name"] = data.title 138 | 139 | else: 140 | tmp = json.loads(privatepath.read_text("utf-8")) 141 | if id in private_conversations: 142 | for c in private_conversations[id]: 143 | if data.conversation_id == c["conversation_id"]: 144 | if data.title: 145 | c["conversation_name"] = data.title 146 | 147 | # if data.gpt_model == "gpt-4": 148 | # tmp[id + '-gpt-4'] = data.conversation_id 149 | # elif data.gpt_model == "gpt-4o": 150 | # tmp[id + '-gpt-4o'] = data.conversation_id 151 | # # pass switch 4.1? 152 | # else: 153 | # tmp[id] = data.conversation_id 154 | 155 | # 不再根据模型隔离会话,同一个会话内也可以切换模型,兼容官网改动 156 | tmp[id] = data.conversation_id 157 | 158 | if c_type == 'group': 159 | grouppath.write_text(json.dumps(tmp)) 160 | group_conversations_path.write_text(json.dumps(group_conversations)) 161 | else: 162 | privatepath.write_text(json.dumps(tmp)) 163 | private_conversations_path.write_text(json.dumps(private_conversations)) 164 | 165 | def upgrade_model(model: str) -> str: 166 | if model in all_free_models_values() and model in all_models_values() and model != all_free_models_values()[0]: 167 | return all_free_models_values()[0] 168 | return model 169 | 170 | 171 | 172 | 173 | async def chat_msg(bot: Bot,event: MessageEvent|QQMessageEvent,chatbot: chatgpt,text: Message|QQMessage = EventMessage()): 174 | '''聊天处理''' 175 | matcher: Matcher = current_matcher.get() 176 | 177 | # bot1 = current_bot 178 | # # bots = bot1.name 179 | # b = bot1.get() 180 | # bots = get_bots() 181 | # bbb = T_BotConnectionHook 182 | await ban_check(event,matcher,text) 183 | data = MsgData() 184 | data.web_search = True 185 | if config_gpt.gpt_chat_start and not config_gpt.gpt_chat_start_in_msg: 186 | chat_start = [gpt_start for gpt_start in config_gpt.gpt_chat_start if event.get_plaintext().startswith(gpt_start)] 187 | if chat_start: 188 | text = Message(text.extract_plain_text()[len(chat_start[0]):]) 189 | 190 | text_handle = text.extract_plain_text() 191 | if isinstance(event,MessageEvent): 192 | if event.reply: 193 | # 被回复不回复 194 | if not config_gpt.gpt_replay_to_replay: 195 | await matcher.finish() 196 | if await name_or_tome(event): 197 | if len(event.raw_message) > 6: 198 | for name in bot_name: 199 | if name in event.raw_message[:6]: 200 | 201 | text_handle = f"{name} {text}" 202 | else: 203 | for name in bot_name: 204 | if name in event.raw_message: 205 | text_handle = f"{name} {text}" 206 | 207 | else: 208 | if not text.extract_plain_text(): 209 | # text 为空 210 | text_handle = f"{bot_name[0]}" 211 | else: 212 | text_handle = text.extract_plain_text() 213 | # 检测plus模型状态 214 | plus_tmp = json.loads(plusstatus.read_text()) 215 | id,value = await get_id_from_all(event) 216 | if id in plus_tmp and plus_tmp['status']: 217 | data.gpt_model = plus_tmp[id] 218 | if config_gpt.gpt_force_upgrade_model: 219 | data.gpt_model = upgrade_model(data.gpt_model) 220 | 221 | # 图片附加消息检测,free账户也可用但额度很低 222 | if config_gpt.gpt_free_image or id in plus_tmp: 223 | msgs = event.get_message() 224 | imgs = [msg for msg in msgs if msg.type == "image"] 225 | if imgs: 226 | for img_msg in imgs: 227 | async with AsyncClient() as client: 228 | res = await client.get(img_msg.data['url']) 229 | data.upload_file.append(IOFile(content=res.content,name=img_msg.data['url'])) 230 | 231 | if isinstance(event,GroupMessageEvent): 232 | data = get_c_id(str(event.group_id),data,'group') 233 | if config_gpt.group_chat: 234 | data.msg_send = f'{event.get_user_id()}对你说:{text_handle}' 235 | else: 236 | data.msg_send=text_handle 237 | # 替换qq 238 | data.msg_send=data.msg_send.replace("CQ:at,qq=","") 239 | data = await chatbot.continue_chat(data) 240 | if not data.error_info or data.status: 241 | set_c_id(str(event.group_id),data,'group') 242 | # group_member = await bot.call_api('get_group_member_list',**{"group_id":event.group_id}) 243 | # data = await group_handle(data,group_member) 244 | data = await group_handle(data,await tools.get_group_member_list(group_id=event.group_id)) 245 | 246 | elif isinstance(event,PrivateMessageEvent): 247 | data = get_c_id(event.get_user_id(),data,'private') 248 | data.msg_send=event.raw_message 249 | data = await chatbot.continue_chat(data) 250 | if not data.error_info or data.status: 251 | set_c_id(event.get_user_id(),data,'private') 252 | elif isinstance(event,QQMessageEvent): 253 | id,value = await get_id_from_guild_group(event) 254 | data = get_c_id(id,data,'group') 255 | data.msg_send=text_handle 256 | data = await chatbot.continue_chat(data) 257 | if not data.error_info or data.status: 258 | set_c_id(id,data,'group') 259 | 260 | if data.error_info and not data.msg_recv: 261 | data.msg_recv = data.error_info 262 | 263 | 264 | 265 | await ban_check(event,matcher,Message(data.msg_recv)) 266 | 267 | imgs = [] 268 | if data.img_list: 269 | logger.debug(f"检测到gpt消息存在图片链接\n {''.join(data.img_list)}") 270 | async with AsyncClient(proxy=config_gpt.gpt_proxy) as client: 271 | for img_url in data.img_list: 272 | try: 273 | res = await client.get(img_url) 274 | if res.status_code == 200: 275 | mime = guess(res.content) 276 | if"image" in mime.mime: 277 | logger.debug(f"链接{img_url}为图片,准备装填") 278 | imgs.append(res.content) 279 | except Exception as e: 280 | logger.warning(f"获取图片 {img_url} 出现异常:{e}") 281 | send_md_status = True 282 | if config_gpt.gpt_lgr_markdown and isinstance(event,MessageEvent): 283 | md_status_tmp = json.loads(mdstatus.read_text()) 284 | if isinstance(event,PrivateMessageEvent): 285 | if event.get_user_id() not in md_status_tmp['private']: 286 | send_md_status = False 287 | else: 288 | id,value = await get_id_from_all(event) 289 | if id not in md_status_tmp['group']: 290 | send_md_status = False 291 | else: 292 | send_md_status = False 293 | 294 | msg = replace_name(data).msg_raw[0] + replace_name(data).msg_raw[2] if replace_name(data).msg_raw and len(data.msg_raw) > 2 else replace_name(data).msg_recv 295 | 296 | if send_md_status and isinstance(event,MessageEvent): 297 | await tools.send_text2md(msg,str(event.self_id)) 298 | if imgs: 299 | await matcher.send(Message([MessageSegment.image(file=img) for img in imgs])) 300 | await matcher.finish() 301 | elif send_md_status and isinstance(event,QQMessageEvent): 302 | #TODO QQ适配器 md模板等兼容发送,待续 303 | pass 304 | elif not send_md_status and isinstance(event,QQMessageEvent): 305 | # QQ适配器正常消息 306 | msg_img = Message([QQMessageSegment.file_image(b64encode(img).decode('utf-8')) for img in imgs]) 307 | elif not send_md_status and isinstance(event,MessageEvent): 308 | # onebot适配器正常消息 309 | msg_img = [MessageSegment.image(file=img) for img in imgs] 310 | if config_gpt.gpt_url_replace and isinstance(event,QQMessageEvent): 311 | if data.msg_raw and len(data.msg_raw) > 2: 312 | data.msg_raw[0] = replace_dot_in_domain(data.msg_raw[0]) 313 | data.msg_raw[2] = replace_dot_in_domain(data.msg_raw[2]) 314 | else: 315 | msg = replace_dot_in_domain(msg) 316 | 317 | if data.msg_raw: 318 | if len(data.msg_raw)>1: 319 | try: 320 | # msg_md_pic = await md_to_pic(''.join(data.msg_raw)) 321 | msg_md_pic = await chatbot.md2img(''.join(data.msg_raw)) 322 | except Exception as e: 323 | logger.warning(f"获取元数据转md图片出错") 324 | if not send_md_status and isinstance(event,QQMessageEvent): 325 | # QQ适配器正常消息 326 | md_img = QQMessageSegment.file_image(b64encode(msg_md_pic).decode('utf-8')) 327 | else: 328 | md_img = MessageSegment.image(file=msg_md_pic) 329 | end_msg = md_img # data.msg_raw[0] + md_img + data.msg_raw[2] 330 | else: 331 | end_msg = msg 332 | else: 333 | end_msg = msg 334 | 335 | if imgs: 336 | all_msg = Message(end_msg)+Message(msg_img) 337 | else: 338 | all_msg = Message(end_msg) 339 | await matcher.finish(all_msg) 340 | 341 | 342 | async def reset_history(event: MessageEvent|QQMessageEvent,chatbot: chatgpt,text:Message|QQMessage = EventMessage()): 343 | '''重置''' 344 | matcher: Matcher = current_matcher.get() 345 | await ban_check(event,matcher) 346 | data = MsgData() 347 | # 检测plus模型状态 348 | plus_tmp = json.loads(plusstatus.read_text()) 349 | id,value = await get_id_from_all(event) 350 | if id in plus_tmp and plus_tmp['status']: 351 | data.gpt_model = plus_tmp[id] 352 | if config_gpt.gpt_force_upgrade_model: 353 | data.gpt_model = upgrade_model(data.gpt_model) 354 | if isinstance(event,PrivateMessageEvent): 355 | data = get_c_id(id,data,'private') 356 | else: 357 | data = get_c_id(id,data,'group') 358 | data = await chatbot.back_init_personality(data) 359 | if isinstance(event,GroupMessageEvent): 360 | data = await group_handle(data,await tools.get_group_member_list(event.group_id)) 361 | if config_gpt.gpt_url_replace and isinstance(event,QQMessageEvent): 362 | data.msg_recv = replace_dot_in_domain(data.msg_recv) 363 | await matcher.finish(replace_name(data).msg_recv) 364 | 365 | async def back_last(event: MessageEvent|QQMessageEvent,chatbot: chatgpt,text:Message|QQMessage = EventMessage()): 366 | '''重置上一句''' 367 | matcher: Matcher = current_matcher.get() 368 | await ban_check(event,matcher) 369 | data = MsgData() 370 | # 检测plus模型状态 371 | plus_tmp = json.loads(plusstatus.read_text()) 372 | id,value = await get_id_from_all(event) 373 | if id in plus_tmp and plus_tmp['status']: 374 | data.gpt_model = plus_tmp[id] 375 | if config_gpt.gpt_force_upgrade_model: 376 | data.gpt_model = upgrade_model(data.gpt_model) 377 | if isinstance(event,PrivateMessageEvent): 378 | data = get_c_id(id,data,'private') 379 | else: 380 | data = get_c_id(id,data,'group') 381 | data.msg_send = "-1" 382 | data = await chatbot.back_chat_from_input(data) 383 | if isinstance(event,GroupMessageEvent): 384 | data = await group_handle(data,await tools.get_group_member_list(event.group_id)) 385 | if config_gpt.gpt_url_replace and isinstance(event,QQMessageEvent): 386 | data.msg_recv = replace_dot_in_domain(data.msg_recv) 387 | await matcher.finish(replace_name(data).msg_recv) 388 | 389 | async def back_anywhere(event: MessageEvent|QQMessageEvent,chatbot:chatgpt,arg: Message|QQMessage): 390 | '''回到过去''' 391 | matcher: Matcher = current_matcher.get() 392 | await ban_check(event,matcher) 393 | data = MsgData() 394 | # 检测plus模型状态 395 | plus_tmp = json.loads(plusstatus.read_text()) 396 | id,value = await get_id_from_all(event) 397 | if id in plus_tmp and plus_tmp['status']: 398 | data.gpt_model = plus_tmp[id] 399 | if config_gpt.gpt_force_upgrade_model: 400 | data.gpt_model = upgrade_model(data.gpt_model) 401 | if isinstance(event,PrivateMessageEvent): 402 | data = get_c_id(id,data,'private') 403 | else: 404 | data = get_c_id(id,data,'group') 405 | data.msg_send = arg.extract_plain_text() 406 | data = await chatbot.back_chat_from_input(data) 407 | if isinstance(event,GroupMessageEvent): 408 | data = await group_handle(data,await tools.get_group_member_list(event.group_id)) 409 | if config_gpt.gpt_url_replace and isinstance(event,QQMessageEvent): 410 | data.msg_recv = replace_dot_in_domain(data.msg_recv) 411 | await matcher.finish(replace_name(data).msg_recv) 412 | 413 | async def init_gpt(event: MessageEvent|QQMessageEvent,chatbot:chatgpt,arg :Message|QQMessage,plus: bool = False): 414 | '''初始化''' 415 | matcher: Matcher = current_matcher.get() 416 | await ban_check(event,matcher) 417 | data = MsgData() 418 | if plus: 419 | data.gpt_plus = True 420 | logger.info(f"当前为plus初始化") 421 | if arg.extract_plain_text() == '': 422 | arg = Message("默认") 423 | # 检测plus模型状态 424 | plus_tmp = json.loads(plusstatus.read_text()) 425 | id,value = await get_id_from_all(event) 426 | if id in plus_tmp and plus_tmp['status']: 427 | data.gpt_model = plus_tmp[id] 428 | if config_gpt.gpt_force_upgrade_model: 429 | data.gpt_model = upgrade_model(data.gpt_model) 430 | person_type = json.loads(personpath.read_text("utf8")) 431 | if " " in arg.extract_plain_text(): 432 | data.msg_send = arg.extract_plain_text().split(" ")[0] 433 | 434 | if person_type[data.msg_send]['open'] != '': 435 | if event.get_user_id() != person_type[data.msg_send]['open']: 436 | await matcher.finish("别人的私有人设不可以用哦") 437 | 438 | if arg.extract_plain_text().split(" ")[1] == "继续": 439 | if isinstance(event,PrivateMessageEvent): 440 | data = get_c_id(id,data,'private') 441 | else: 442 | data = get_c_id(id,data,'group') 443 | else: 444 | data.msg_send = arg.extract_plain_text() 445 | if person_type[data.msg_send]['open'] != '': 446 | if event.get_user_id() != person_type[data.msg_send]['open']: 447 | await matcher.finish("别人的私有人设不可以用哦") 448 | 449 | if isinstance(event,GroupMessageEvent): 450 | if person_type[data.msg_send]['r18']: 451 | if event.sender.role != "owner" and event.sender.role != "admin": 452 | await matcher.finish("在群里仅群管理员可初始化r18人设哦") 453 | data = await chatbot.init_personality(data) 454 | 455 | if not data.msg_recv: 456 | await matcher.finish( f"初始化失败,错误为:\n{data.error_info}") 457 | if isinstance(event,PrivateMessageEvent): 458 | set_c_id(id,data,'private') 459 | else: 460 | set_c_id(id,data,'group') 461 | if isinstance(event,GroupMessageEvent): 462 | data = await group_handle(data,await tools.get_group_member_list(event.group_id)) 463 | await ban_check(event,matcher,Message(data.msg_recv)) 464 | 465 | # 保存会话标题 来源信息 466 | current_time = datetime.now() 467 | 468 | conversation_info = { 469 | "init_time": current_time.strftime('%Y-%m-%d %H:%M:%S'), 470 | "conversation_name": data.title, 471 | "conversation_id": data.conversation_id, 472 | "from_email": data.from_email, 473 | } 474 | 475 | group_conversations = json.loads(group_conversations_path.read_text()) 476 | private_conversations = json.loads(private_conversations_path.read_text()) 477 | 478 | if isinstance(event,QQMessageEvent): 479 | if id not in group_conversations: 480 | group_conversations[id] = [conversation_info] 481 | else: 482 | group_conversations[id].insert(0,conversation_info) 483 | if len(group_conversations[id]) > 30: 484 | group_conversations[id].pop() 485 | group_conversations_path.write_text(json.dumps(group_conversations)) 486 | 487 | if config_gpt.gpt_url_replace: 488 | data.msg_recv = replace_dot_in_domain(data.msg_recv) 489 | await matcher.finish(replace_name(data).msg_recv) 490 | else: 491 | msg = Message(MessageSegment.node_custom(user_id=event.self_id,nickname=arg.extract_plain_text(),content=Message(replace_name(data).msg_recv))) 492 | if isinstance(event,GroupMessageEvent): 493 | if id not in group_conversations: 494 | group_conversations[id] = [conversation_info] 495 | else: 496 | group_conversations[id].insert(0,conversation_info) 497 | if len(group_conversations[id]) > 30: 498 | group_conversations[id].pop() 499 | group_conversations_path.write_text(json.dumps(group_conversations)) 500 | await tools.send_group_forward_msg_by_bots_once(group_id=event.group_id,node_msg=msg,bot_id=str(event.self_id)) 501 | else: 502 | if id not in private_conversations: 503 | private_conversations[id] = [conversation_info] 504 | else: 505 | private_conversations[id].insert(0,conversation_info) 506 | if len(private_conversations[id]) > 30: 507 | private_conversations[id].pop() 508 | private_conversations_path.write_text(json.dumps(private_conversations)) 509 | await tools.send_private_forward_msg_by_bots_once(user_id=event.user_id,node_msg=msg,bot_id=str(event.self_id)) 510 | await matcher.finish() 511 | 512 | async def ps_list(event: MessageEvent|QQMessageEvent,chatbot: chatgpt): 513 | '''人设列表''' 514 | matcher: Matcher = current_matcher.get() 515 | await ban_check(event,matcher) 516 | if isinstance(event,MessageEvent): 517 | person_list = [MessageSegment.node_custom(user_id=event.self_id,nickname="0",content=Message(MessageSegment.text("序号 人设名 r18 公开")))] 518 | else: 519 | person_list = "\n|序号|人设名|r18|公开|\n|:----:|:------:|:------:|:------:|\n" 520 | person_type = json.loads(personpath.read_text("utf8")) 521 | if person_type == {}: 522 | await matcher.finish("还没有人设") 523 | for index,x in enumerate(chatbot.personality.init_list): 524 | r18 = "是" if person_type[x.get('name')]['r18'] else "否" 525 | open = "否" if person_type[x.get('name')]['open'] else "是" 526 | if isinstance(event,MessageEvent): 527 | person_list.append(MessageSegment.node_custom(user_id=event.self_id,nickname="0",content=Message(MessageSegment.text(f"{(index+1):02} {x.get('name')} {r18} {open} ")))) # type: ignore 528 | else: 529 | person_list += f"|{(index+1):03}|{x.get('name')}|{r18}|{open}|\n" # type: ignore 530 | 531 | if isinstance(event,MessageEvent): 532 | if isinstance(event,GroupMessageEvent): 533 | await tools.send_group_forward_msg_by_bots_once(group_id=event.group_id,node_msg=person_list,bot_id=str(event.self_id)) # type: ignore 534 | else: 535 | await tools.send_private_forward_msg_by_bots_once(user_id=event.user_id,node_msg=person_list,bot_id=str(event.self_id)) # type: ignore 536 | else: 537 | if isinstance(event,QQGroupAtMessageCreateEvent): 538 | await matcher.finish(person_list.replace("|:----:|:------:|:------:|:------:|\n","")) # type: ignore 539 | img = await md_to_pic(person_list) # type: ignore 540 | # img = await chatbot.md2img(person_list) 541 | await matcher.finish(QQMessageSegment.file_image(img)) 542 | await matcher.finish() 543 | 544 | async def cat_ps(event: MessageEvent|QQMessageEvent,chatbot: chatgpt,arg: Message|QQMessage): 545 | '''查看人设''' 546 | matcher: Matcher = current_matcher.get() 547 | await ban_check(event,matcher) 548 | if arg.extract_plain_text(): 549 | person_type = json.loads(personpath.read_text("utf8")) 550 | if arg.extract_plain_text() not in person_type: 551 | await matcher.finish("没有找到哦,请检查名字是否正确") 552 | # if event.get_user_id() != person_type[arg.extract_plain_text()]['open'] or '' != person_type[arg.extract_plain_text()]['open']: 553 | # await matcher.finish("别人的私有人设不可以看哦") 554 | if person_type[arg.extract_plain_text()]['open'] != '': 555 | if event.get_user_id() != person_type[arg.extract_plain_text()]['open']: 556 | await matcher.finish("别人的私有人设不可以用哦") 557 | value = chatbot.personality.get_value_by_name(arg.extract_plain_text()) 558 | if not value: 559 | await matcher.finish("没有找到哦,请检查名字是否正确") 560 | if isinstance(event,MessageEvent): 561 | msg = Message(MessageSegment.node_custom(user_id=event.self_id,nickname=arg.extract_plain_text(),content=Message(value))) 562 | if isinstance(event,GroupMessageEvent): 563 | await tools.send_group_forward_msg_by_bots_once(group_id=event.group_id,node_msg=msg,bot_id=str(event.self_id)) 564 | else: 565 | await tools.send_private_forward_msg_by_bots_once(user_id=event.user_id,node_msg=msg,bot_id=str(event.self_id)) 566 | else: 567 | await matcher.finish(value) 568 | else: 569 | await matcher.finish("好像没有输入名字哦") 570 | 571 | async def add_ps1(event: MessageEvent|QQMessageEvent,status: T_State,arg :Message|QQMessage): 572 | '''添加人设,步骤1''' 573 | matcher: Matcher = current_matcher.get() 574 | await ban_check(event,matcher) 575 | status["id"] = event.get_user_id() 576 | try: 577 | if arg.extract_plain_text(): 578 | status["name"] = arg.extract_plain_text() 579 | 580 | ban_str_tmp = ban_str_path.read_text("utf-8").splitlines() 581 | if str(status["name"]) in ban_str_tmp: 582 | # 触发屏蔽词 583 | await add_ban(event.get_user_id(),str(status["name"])) 584 | await matcher.finish("检测到屏蔽词,已屏蔽") 585 | if len(status["name"]) > 15: 586 | await matcher.finish("名字不可以超过15字") 587 | elif len(status["name"]) == 0: 588 | await matcher.finish("名字不可以为空") 589 | if status["name"] in json.loads(personpath.read_text("utf8")): 590 | await matcher.finish("这个人设名已存在哦,换一个吧") 591 | else: 592 | pass 593 | except Exception as e: 594 | logger.info(e) 595 | 596 | async def add_ps2(status: T_State,name: Message|QQMessage): 597 | '''添加人设,步骤2''' 598 | event = current_event.get() 599 | matcher: Matcher = current_matcher.get() 600 | if name: 601 | if type(name) == str: 602 | pass 603 | else: 604 | 605 | if name.extract_plain_text(): 606 | status["name"] = name.extract_plain_text() 607 | 608 | ban_str_tmp = ban_str_path.read_text("utf-8").splitlines() 609 | if str(status["name"]) in ban_str_tmp: 610 | # 触发屏蔽词 611 | await add_ban(event.get_user_id(),str(status["name"])) 612 | await matcher.finish("检测到屏蔽词,已屏蔽") 613 | if len(status["name"]) > 15: 614 | await matcher.finish("名字不可以超过15字") 615 | elif len(status["name"]) == 0: 616 | await matcher.finish("名字不可以为空") 617 | if status["name"] in json.loads(personpath.read_text("utf8")): 618 | await matcher.finish("这个人设名已存在哦,换一个吧") 619 | status["name"] = name.extract_plain_text() 620 | else: 621 | await matcher.finish("名字不可以为空(也许是与bot同名了)") 622 | else: 623 | await matcher.finish("输入错误了,添加结束。") 624 | 625 | async def add_ps3(status: T_State,r18: Message|QQMessage): 626 | '''添加人设,步骤3''' 627 | if r18.extract_plain_text() == "是": 628 | status["r18"] = True 629 | elif r18.extract_plain_text() == "否": 630 | status["r18"] = False 631 | else: 632 | matcher: Matcher = current_matcher.get() 633 | await matcher.finish("输入错误了,添加结束。") 634 | 635 | async def add_ps4(status: T_State,open: Message|QQMessage): 636 | '''添加人设,步骤4''' 637 | if open.extract_plain_text() == "公开" : 638 | status["open"] = "" 639 | elif open.extract_plain_text() == "私有": 640 | status["open"] = status["id"] 641 | else: 642 | matcher: Matcher = current_matcher.get() 643 | await matcher.finish("输入错误了,添加结束。") 644 | 645 | async def add_ps5(status: T_State,value: Message|QQMessage,chatbot: chatgpt): 646 | '''添加人设,步骤5''' 647 | status["value"] = value 648 | personality = { 649 | "name":str(status["name"]), 650 | "r18":status["r18"], 651 | "open":status["open"], 652 | "value":str(status["value"]), 653 | 654 | } 655 | matcher: Matcher = current_matcher.get() 656 | ban_str_tmp = ban_str_path.read_text("utf-8").splitlines() 657 | for x in ban_str_tmp: 658 | if x in str(status["value"]): 659 | # 触发屏蔽词 660 | await matcher.finish("存在违禁词") 661 | await chatbot.add_personality(personality) 662 | person_type = json.loads(personpath.read_text("utf8")) 663 | person_type[personality["name"]] = { 664 | "r18":personality["r18"], 665 | "open":personality["open"] 666 | } 667 | 668 | personpath.write_text(json.dumps(person_type)) 669 | 670 | await matcher.finish(await chatbot.show_personality_list()) 671 | 672 | 673 | async def add_default_ps(chatbot: chatgpt): 674 | '''添加人设''' 675 | personality = { 676 | "name":"默认", 677 | "r18":False, 678 | "open":"", 679 | "value":"你好", 680 | 681 | } 682 | person_type = json.loads(personpath.read_text("utf8")) 683 | if personality["name"] not in person_type: 684 | await chatbot.add_personality(personality) 685 | person_type = json.loads(personpath.read_text("utf8")) 686 | person_type[personality["name"]] = { 687 | "r18":personality["r18"], 688 | "open":personality["open"] 689 | } 690 | 691 | personpath.write_text(json.dumps(person_type)) 692 | 693 | 694 | async def del_ps(event: MessageEvent|QQMessageEvent,chatbot: chatgpt,arg :Message|QQMessage): 695 | '''删除人设''' 696 | matcher: Matcher = current_matcher.get() 697 | person_type = json.loads(personpath.read_text("utf8")) 698 | try: 699 | del person_type[arg.extract_plain_text()] 700 | personpath.write_text(json.dumps(person_type)) 701 | except Exception: 702 | await matcher.finish("没有找到这个人设") 703 | await matcher.finish(await chatbot.del_personality(arg.extract_plain_text())) 704 | 705 | async def chatmsg_history(bot: Bot,event: MessageEvent|QQMessageEvent,chatbot: chatgpt,text:Message|QQMessage = EventMessage()): 706 | '''历史记录''' 707 | data = MsgData() 708 | # 检测plus模型状态 709 | # plus_tmp = json.loads(plusstatus.read_text()) 710 | id,value = await get_id_from_all(event) 711 | # if id in plus_tmp and plus_tmp['status']: 712 | # data.gpt_model = plus_tmp[id] 713 | if isinstance(event,PrivateMessageEvent): 714 | data = get_c_id(id,data,'private') 715 | else: 716 | data = get_c_id(id,data,'group') 717 | matcher: Matcher = current_matcher.get() 718 | if not data.conversation_id: 719 | await matcher.finish("还没有聊天记录") 720 | 721 | def to_num(snum: str) -> int: 722 | num = 0 723 | try: 724 | num = int(snum) 725 | except: 726 | logger.debug(f"{snum} not int") 727 | return num 728 | left_num = 1 729 | right_num = None 730 | if "-" in text.extract_plain_text() and text.extract_plain_text().count("-") == 1: 731 | if text.extract_plain_text().split("-")[0]: 732 | num = to_num(text.extract_plain_text().split("-")[0]) 733 | left_num = num if num != 0 else 1 734 | if text.extract_plain_text().split("-")[1]: 735 | num = to_num(text.extract_plain_text().split("-")[1]) 736 | right_num = num if num != 0 else 1 737 | elif ":" in text.extract_plain_text() and text.extract_plain_text().count(":") == 1: 738 | if text.extract_plain_text().split(":")[0]: 739 | num = to_num(text.extract_plain_text().split(":")[0]) 740 | left_num = num if num != 0 else 1 741 | if text.extract_plain_text().split(":")[1]: 742 | num = to_num(text.extract_plain_text().split(":")[1]) 743 | right_num = num if num != 0 else 1 744 | else: 745 | if text.extract_plain_text: 746 | num = to_num(text.extract_plain_text()) 747 | left_num = num if num!= 0 else 1 748 | right_num = num+1 if num!=0 else None 749 | 750 | chat_his = [MessageSegment.node_custom(user_id=event.self_id,nickname=str(index),content=Message(f"### index `{history['index']}`\n---\n```next_msg_id\n{history['next_msg_id']}\n---\n```Q\n{history['Q']}\n---\n```A\n{history['A']}```")) for index,history in enumerate(await chatbot.show_chat_history(data))][left_num:right_num] 751 | if chat_his == []: 752 | await matcher.finish("还没有开始聊天") 753 | if isinstance(event,GroupMessageEvent): 754 | # await bot.send_group_forward_msg(group_id=event.group_id, messages=chat_his) 755 | await tools.send_group_forward_msg_by_bots_once(group_id=event.group_id,node_msg=chat_his,bot_id=str(event.self_id)) 756 | elif isinstance(event,PrivateMessageEvent): 757 | # await bot.send_private_forward_msg(user_id=event.user_id, messages=chat_his) 758 | await tools.send_private_forward_msg_by_bots_once(user_id=event.user_id,node_msg=chat_his,bot_id=str(event.self_id)) 759 | 760 | elif isinstance(event,QQMessageEvent): 761 | res = await chatbot.show_chat_history(data) 762 | if config_gpt.gpt_url_replace: 763 | send_msg = replace_dot_in_domain('\n'.join(res)) 764 | else: 765 | send_msg = '\n'.join(res) 766 | await matcher.finish(send_msg) 767 | 768 | await matcher.finish() 769 | 770 | async def chatmsg_history_tree(event: MessageEvent|QQMessageEvent,chatbot: chatgpt,text:Message|QQMessage = EventMessage()): 771 | '''历史记录树''' 772 | data = MsgData() 773 | id,value = await get_id_from_all(event) 774 | if isinstance(event,PrivateMessageEvent): 775 | data = get_c_id(id,data,'private') 776 | else: 777 | data = get_c_id(id,data,'group') 778 | matcher: Matcher = current_matcher.get() 779 | if not data.conversation_id: 780 | await matcher.finish("还没有聊天记录") 781 | tree = await chatbot.show_history_tree_md(msg_data=data) 782 | # pic = await md_to_pic(tree) 783 | pic = await chatbot.md2img(tree) 784 | await matcher.finish(MessageSegment.image(file=pic)) 785 | 786 | async def status_pic(matcher: Matcher,chatbot: chatgpt): 787 | '''工作状态''' 788 | try: 789 | tmp = await chatbot.token_status() 790 | if len(tmp["token"]) != len(tmp["work"]): 791 | await matcher.finish("似乎还没启动完咩") 792 | except Exception as e: 793 | logger.debug(e) 794 | await matcher.finish() 795 | msg = f"""### 白名单模式:`{'已开启' if config_gpt.gpt_white_list_mode else '已关闭'}` 796 | --- 797 | ### plus白名单模式:`{'已开启' if config_gpt.gptplus_white_list_mode else '已关闭'}` 798 | --- 799 | ### 多人识别:`{'已开启' if config_gpt.group_chat else '已关闭'}` 800 | --- 801 | """ 802 | msg += "\n|序号|存活|工作状态|历史会话|plus|账户|\n|:----:|:------:|:------:|:------:|:------:|:------:|\n" 803 | for index,x in enumerate(tmp["token"]): 804 | if len(tmp['cid_num']) < len(tmp["token"]): 805 | for num in range(0,len(tmp["token"])-len(tmp['cid_num'])): 806 | tmp['cid_num'] += ['0'] 807 | msg += f"|{(index+1):03}|{x}|{tmp['work'][index]}| {int(tmp['cid_num'][index]):03}|{tmp['plus'][index]}|{tmp['account'][index]}|\n" 808 | 809 | event = current_event.get() 810 | img = await md_to_pic(msg) 811 | # img = await chatbot.md2img(msg) 812 | if isinstance(event,QQGroupAtMessageCreateEvent): 813 | await matcher.finish(QQMessageSegment.file_image(b64encode(img).decode('utf-8'))) # type: ignore 814 | elif isinstance(event,MessageEvent): 815 | await matcher.finish(MessageSegment.image(file=img)) 816 | else: 817 | await matcher.finish(QQMessageSegment.file_image(img)) 818 | 819 | async def black_list(chatbot: chatgpt,event: MessageEvent|QQMessageEvent,arg :Message|QQMessage): 820 | '''黑名单列表''' 821 | matcher: Matcher = current_matcher.get() 822 | ban_tmp = json.loads(banpath.read_text("utf-8")) 823 | msgs_head = ["\n|账户|内容|","|:------:|:------:|"] 824 | msgs = [] 825 | if arg.extract_plain_text(): 826 | if arg.extract_plain_text() in ban_tmp: 827 | f_tmp = ban_tmp[arg.extract_plain_text()][0].replace('"',r'\"').replace("\n"," ") 828 | msgs.append(f"|{arg.extract_plain_text()}|{f_tmp}|") 829 | else: 830 | for x in ban_tmp: 831 | f_tmp = ban_tmp[x][0].replace('"',r'\"').replace("\n"," ") 832 | msgs.append(f"|{x}|{f_tmp}|") 833 | imgs = [] 834 | if len(msgs) > 100: 835 | chunks = list(chunked(msgs,100)) 836 | for chunk in chunks: 837 | tmp = msgs_head.copy() 838 | tmp.extend(chunk) 839 | imgs.append(await md_to_pic('\n'.join(tmp), width=650)) 840 | else: 841 | imgs.append(await md_to_pic('\n'.join(msgs_head + msgs), width=650)) 842 | if isinstance(event,QQGroupAtMessageCreateEvent): 843 | #qq适配器的QQ群,暂不支持直接发送图片 (x 现在能发了) 844 | msg = QQMessage([QQMessageSegment.file_image(b64encode(img).decode('utf-8')) for img in imgs]) # type: ignore 845 | await matcher.finish(msg) # type: ignore 846 | elif isinstance(event,MessageEvent): 847 | msg = Message([MessageSegment.image(file=img) for img in imgs]) 848 | await matcher.finish(msg) 849 | else: 850 | msg = QQMessage([QQMessageSegment.file_image(img) for img in imgs]) 851 | await matcher.finish(msg) 852 | 853 | async def remove_ban_user(arg: Message|QQMessage): 854 | ''''解黑''' 855 | matcher: Matcher = current_matcher.get() 856 | ban_tmp = json.loads(banpath.read_text("utf-8")) 857 | try: 858 | del ban_tmp[arg.extract_plain_text()] 859 | banpath.write_text(json.dumps(ban_tmp)) 860 | except Exception: 861 | await matcher.finish("失败") 862 | 863 | await matcher.finish("成功") 864 | 865 | async def add_white_list(arg: Message|QQMessage): 866 | '''OneBot适配器加白 传入消息则消息为目标群号,传str则它为群号''' 867 | matcher: Matcher = current_matcher.get() 868 | ban_tmp = json.loads(banpath.read_text("utf-8")) 869 | id = "" 870 | if isinstance(arg, QQMessage): 871 | id,this_type = await get_id_from_guild_group(event=current_event.get()) # type: ignore 872 | else: 873 | this_type = "group" 874 | plus = False 875 | if arg.extract_plain_text().startswith("plus"): 876 | plus = True 877 | arg = Message(arg.extract_plain_text()[4:]) 878 | if " " in arg.extract_plain_text(): 879 | sp = arg.extract_plain_text().split(" ") 880 | id = sp[0] 881 | this_type = sp[1] 882 | if this_type not in ["group","private","群","个人"]: 883 | await matcher.finish("白名单类型错误了,仅支持 群 / 个人,不输入默认为群") 884 | this_type = "group" if this_type == "群" else "private" 885 | else: 886 | id = arg.extract_plain_text() 887 | 888 | if id in ban_tmp: 889 | await matcher.finish("对方在黑名单中哦,真的要继续吗?") 890 | 891 | await matcher.finish(await add_white(id, this_type, plus)) 892 | 893 | async def del_white_list(arg: Message|QQMessage|str): 894 | '''删除白名单''' 895 | matcher: Matcher = current_matcher.get() 896 | id = "" 897 | this_type = "group" 898 | event = current_event.get() 899 | if isinstance(event,QQGroupAtMessageCreateEvent): 900 | this_type = "qqgroup" 901 | elif isinstance(event,QQAtMessageCreateEvent): 902 | this_type = "qqguild" 903 | if isinstance(arg,str): 904 | id = arg 905 | else: 906 | if " " in arg.extract_plain_text(): 907 | sp = arg.extract_plain_text().split(" ") 908 | id = sp[0] 909 | this_type = sp[1] 910 | if this_type not in ["group","private","群","个人"]: 911 | await matcher.finish("白名单类型错误了,仅支持 群 / 个人,不输入默认为群") 912 | this_type = "group" if this_type == "群" else "private" 913 | else: 914 | id = arg.extract_plain_text() 915 | 916 | await matcher.finish(await del_white(id, this_type)) 917 | 918 | async def white_list(chatbot: chatgpt): 919 | '''获取白名单列表''' 920 | matcher: Matcher = current_matcher.get() 921 | white_tmp = json.loads(whitepath.read_text("utf-8")) 922 | cdk_list = json.loads(cdklistpath.read_text()) 923 | cdk_source = json.loads(cdksource.read_text()) 924 | combined_dict = {cdk_list[key]: cdk_source[key] for key in cdk_list if key in cdk_source} 925 | plus_status_tmp = json.loads(plusstatus.read_text()) 926 | all_white_ids = {id for ids in white_tmp.values() for id in ids} 927 | msg = "\n|类型|账号|plus|\n|:------:|:------:|:------:|\n" 928 | for x in white_tmp: 929 | for id in white_tmp[x]: 930 | if id in combined_dict: 931 | if id in plus_status_tmp: 932 | msg += f"|{x}|{str(id)}({combined_dict[id]})|plus|\n" 933 | else: 934 | msg += f"|{x}|{str(id)}({combined_dict[id]})| |\n" 935 | else: 936 | if id in plus_status_tmp: 937 | msg += f"|{x}|{str(id)}|plus|\n" 938 | else: 939 | msg += f"|{x}|{str(id)}| |\n" 940 | for id in plus_status_tmp: 941 | if id not in all_white_ids and id != 'status': 942 | msg += f"|unknown|{str(id)}|only plus|\n" 943 | event = current_event.get() 944 | white_list_img = await md_to_pic(msg, width=650) 945 | white_list_img = awa 946 | text = f"当前 3.5 白名单状态:{'开启' if config_gpt.gpt_white_list_mode else '关闭'}\n当前 plus 白名单状态:{'开启' if config_gpt.gptplus_white_list_mode else '关闭'}\n注意:两种白名单模式独立生效" 947 | if isinstance(event,QQGroupAtMessageCreateEvent): 948 | #qq适配器的QQ群,暂不支持直接发送图片 (x 现在能发了) 949 | await matcher.finish(QQMessageSegment.text(text) + QQMessageSegment.file_image(b64encode(white_list_img).decode('utf-8'))) # type: ignore 950 | elif isinstance(event,MessageEvent): 951 | await matcher.finish(MessageSegment.text(text) + MessageSegment.image(file=white_list_img)) 952 | else: 953 | await matcher.finish(QQMessageSegment.text(text) + QQMessageSegment.file_image(white_list_img)) 954 | 955 | async def md_status(event: MessageEvent|QQMessageEvent,arg: Message|QQMessage): 956 | '''md开关''' 957 | md_status_tmp = json.loads(mdstatus.read_text()) 958 | matcher: Matcher = current_matcher.get() 959 | if isinstance(event,PrivateMessageEvent): 960 | # 私聊协议bot 961 | if arg.extract_plain_text().strip() == "开启": 962 | if event.get_user_id() in md_status_tmp["private"]: 963 | await matcher.finish("已经开启过了") 964 | else: 965 | md_status_tmp["private"].append(event.get_user_id()) 966 | elif arg.extract_plain_text().strip() == "关闭": 967 | if event.get_user_id() not in md_status_tmp["private"]: 968 | await matcher.finish("已经关闭过了") 969 | else: 970 | md_status_tmp["private"].remove(event.get_user_id()) 971 | else: 972 | await matcher.finish("指令不正确,请输入 md状态开启 或 md状态关闭") 973 | 974 | else: 975 | if isinstance(event,GroupMessageEvent): 976 | # 群协议bot,仅管理员 977 | if event.sender.role != "owner" and event.sender.role != "admin": 978 | await matcher.finish("在群内仅群管理员可修改md状态") 979 | id,value = await get_id_from_all(event) 980 | if arg.extract_plain_text().strip() == "开启": 981 | if id in md_status_tmp["group"]: 982 | await matcher.finish("已经开启过了") 983 | else: 984 | md_status_tmp["group"].append(id) 985 | elif arg.extract_plain_text().strip() == "关闭": 986 | if id not in md_status_tmp["group"]: 987 | await matcher.finish("已经关闭过了") 988 | else: 989 | md_status_tmp["group"].remove(id) 990 | else: 991 | await matcher.finish("指令不正确,输入 md状态开启 或 md状态关闭") 992 | 993 | 994 | mdstatus.write_text(json.dumps(md_status_tmp)) 995 | await matcher.finish("状态修改成功") 996 | 997 | 998 | async def random_cdk_api(arg: QQMessage): 999 | '''生成用户可用的cdk''' 1000 | matcher: Matcher = current_matcher.get() 1001 | if not arg.extract_plain_text(): 1002 | logger.debug("cdk需要申请人信息") 1003 | await matcher.finish("cdk需要申请人信息") 1004 | key = uuid.uuid4().hex 1005 | # cdk_list 存储cdk对应QQ适配器群聊ID 1006 | cdk_list = json.loads(cdklistpath.read_text()) 1007 | # cdk_source 存储cdk对应申请人信息 1008 | cdk_source = json.loads(cdksource.read_text()) 1009 | cdk_list[key] = None 1010 | cdk_source[key] = arg.extract_plain_text() 1011 | cdklistpath.write_text(json.dumps(cdk_list)) 1012 | cdksource.write_text(json.dumps(cdk_source)) 1013 | # 生成cdk 和 将该cdk绑定到申请人(qq或者群),作为记录 1014 | await matcher.finish(key) 1015 | 1016 | async def add_checker_api(event: QQMessageEvent,arg: QQMessage): 1017 | '''QQ适配器用户自添加白名单''' 1018 | matcher: Matcher = current_matcher.get() 1019 | # 先验cdk列表 1020 | cdk_list = json.loads(cdklistpath.read_text()) 1021 | key = arg.extract_plain_text() 1022 | if key not in cdk_list: 1023 | # 没这个key 1024 | await matcher.finish() 1025 | if cdk_list[key]: 1026 | # 这个key绑定过了 1027 | await matcher.finish() 1028 | id,value = await get_id_from_guild_group(event) 1029 | cdk_list[key] = id 1030 | cdklistpath.write_text(json.dumps(cdk_list)) 1031 | 1032 | # 再弄白名单列表 1033 | await add_white_list(QQMessage(id)) 1034 | 1035 | async def add_plus(arg: Message|QQMessage): 1036 | '''超管添加用户plus''' 1037 | plus_status_tmp = json.loads(plusstatus.read_text()) 1038 | matcher: Matcher = current_matcher.get() 1039 | if arg.extract_plain_text() in plus_status_tmp: 1040 | await matcher.finish(f"{arg.extract_plain_text()} 已经添加过了") 1041 | plus_status_tmp[arg.extract_plain_text()] = all_models_values()[0] 1042 | plusstatus.write_text(json.dumps(plus_status_tmp)) 1043 | await matcher.finish(f"{arg.extract_plain_text()} plus 添加完成") 1044 | 1045 | async def del_plus(arg: Message|QQMessage): 1046 | '''超管删除用户plus''' 1047 | plus_status_tmp = json.loads(plusstatus.read_text()) 1048 | matcher: Matcher = current_matcher.get() 1049 | if arg.extract_plain_text() not in plus_status_tmp: 1050 | await matcher.finish(f"{arg.extract_plain_text()} 并不在plus白名单内") 1051 | del plus_status_tmp[arg.extract_plain_text()] 1052 | plusstatus.write_text(json.dumps(plus_status_tmp)) 1053 | await matcher.finish(f"{arg.extract_plain_text()} plus 删除完成") 1054 | 1055 | async def plus_change(event: MessageEvent|QQMessageEvent,arg: Message|QQMessage): 1056 | '''plus用户切换模型''' 1057 | plus_status_tmp = json.loads(plusstatus.read_text()) 1058 | matcher: Matcher = current_matcher.get() 1059 | if not plus_status_tmp['status']: 1060 | await matcher.finish('超管已关闭plus使用') 1061 | id,value = await get_id_from_all(event) 1062 | data = MsgData() 1063 | data = get_c_id(id,data,value) 1064 | group_conversations = json.loads(group_conversations_path.read_text()) 1065 | private_conversations = json.loads(private_conversations_path.read_text()) 1066 | 1067 | if isinstance(event, PrivateMessageEvent): 1068 | if id not in private_conversations: 1069 | await matcher.finish("当前会话未记录plus账号信息,请重新 plus初始化 后再试") 1070 | for session in config_gpt.gpt_session: 1071 | for conversation in private_conversations[id]: 1072 | if conversation["conversation_id"] == data.conversation_id: 1073 | if session["email"] == conversation['from_email']: 1074 | if not session["gptplus"]: 1075 | await matcher.finish(f"当前会话所属账号信息{session["email"]} 不是标注的plus账号,请重新 plus初始化 切换账号后再试") 1076 | else: 1077 | if id not in group_conversations: 1078 | await matcher.finish("当前会话未记录plus账号信息,请重新 plus初始化 后再试") 1079 | for session in config_gpt.gpt_session: 1080 | for conversation in group_conversations[id]: 1081 | if conversation["conversation_id"] == data.conversation_id: 1082 | if session["email"] == conversation['from_email']: 1083 | if not session["gptplus"]: 1084 | await matcher.finish(f"当前会话所属账号信息{session["email"]} 不是标注的plus账号,请重新 plus初始化 切换账号后再试") 1085 | 1086 | if arg.extract_plain_text() in all_models_keys(True): 1087 | plus_status_tmp[id] = get_model_by_key(arg.extract_plain_text(), True) 1088 | else: 1089 | await matcher.finish(f"请输入正确的模型名:{' '.join(all_models_keys(True))}") 1090 | 1091 | plusstatus.write_text(json.dumps(plus_status_tmp)) 1092 | await matcher.finish(f"plus状态变更为 {arg.extract_plain_text()}") 1093 | 1094 | async def conversations_list(chatbot: chatgpt,event: MessageEvent|QQMessageEvent): 1095 | matcher: Matcher = current_matcher.get() 1096 | id,value = await get_id_from_all(event) 1097 | 1098 | group_conversations = json.loads(group_conversations_path.read_text()) 1099 | private_conversations = json.loads(private_conversations_path.read_text()) 1100 | 1101 | if isinstance(event, PrivateMessageEvent): 1102 | if id not in private_conversations: 1103 | await matcher.finish("当前会话未记录,将在下一次初始化后才开始记录") 1104 | c_list = private_conversations[id] 1105 | else: 1106 | if id not in group_conversations: 1107 | await matcher.finish("当前会话未记录,将在下一次初始化后才开始记录") 1108 | c_list = group_conversations[id] 1109 | all_list = "\n|序号|账号|标题|id|\n|:------:|:------:|:------:|:------:|\n" 1110 | 1111 | all_list += ''.join([f"|{str(i+1)}|{x['from_email']}|{x['conversation_name']}|{x['conversation_id']}|\n" for i,x in enumerate(c_list)]) 1112 | pic = await md_to_pic(all_list) 1113 | # pic = await chatbot.md2img(all_list) 1114 | if isinstance(event,QQGroupAtMessageCreateEvent): 1115 | #qq适配器的QQ群,暂不支持直接发送图片 (x 现在能发了) 1116 | await matcher.finish(QQMessageSegment.file_image(b64encode(pic).decode('utf-8'))) # type: ignore 1117 | else: 1118 | await matcher.finish(MessageSegment.image(file=pic)) 1119 | 1120 | async def conversation_change(event: MessageEvent|QQMessageEvent,arg: Message|QQMessage): 1121 | matcher: Matcher = current_matcher.get() 1122 | try: 1123 | num = int(arg.extract_plain_text()) 1124 | if num > 30 or num < 0: 1125 | raise IndexError 1126 | except ValueError: 1127 | await matcher.finish("切换会话请输入对应序号(1-30)") 1128 | except IndexError: 1129 | await matcher.finish("切换会话请输入对应序号,仅支持 1-30") 1130 | except Exception as e: 1131 | await matcher.finish(f"切换会话 参数错误:{e},仅支持 1-30") 1132 | 1133 | id,value = await get_id_from_all(event) 1134 | 1135 | group_conversations = json.loads(group_conversations_path.read_text()) 1136 | private_conversations = json.loads(private_conversations_path.read_text()) 1137 | 1138 | data = MsgData() 1139 | if isinstance(event, PrivateMessageEvent): 1140 | if id not in private_conversations: 1141 | await matcher.finish("当前会话未记录,将在下一次初始化后才开始记录") 1142 | data.conversation_id = private_conversations[id][num-1]["conversation_id"] 1143 | data.title = private_conversations[id][num-1]["conversation_name"] 1144 | else: 1145 | if id not in group_conversations: 1146 | await matcher.finish("当前会话未记录,将在下一次初始化后才开始记录") 1147 | data.conversation_id = group_conversations[id][num-1]["conversation_id"] 1148 | data.title = group_conversations[id][num-1]["conversation_name"] 1149 | 1150 | set_c_id(id,data,value) 1151 | logger.info(f"id:{id} 切换会话为 {data.title} {data.conversation_id}") 1152 | await matcher.finish(f"切换会话 {data.title} {data.conversation_id} 完成") 1153 | 1154 | 1155 | async def plus_all_status(arg: Message|QQMessage): 1156 | '''超管全局plus状态变更''' 1157 | plus_status_tmp = json.loads(plusstatus.read_text()) 1158 | matcher: Matcher = current_matcher.get() 1159 | if arg.extract_plain_text() == '开启': 1160 | if plus_status_tmp['status'] == True: 1161 | await matcher.finish("已经开启过了") 1162 | plus_status_tmp['status'] = True 1163 | elif arg.extract_plain_text() == '关闭': 1164 | if plus_status_tmp['status'] == False: 1165 | await matcher.finish("已经关闭过了") 1166 | plus_status_tmp['status'] = False 1167 | else: 1168 | await matcher.finish("仅支持 开启/关闭") 1169 | plusstatus.write_text(json.dumps(plus_status_tmp)) 1170 | await matcher.finish(f"全局plus状态 {arg.extract_plain_text()} 完成") 1171 | 1172 | 1173 | def replace_dot_in_domain(text: str): 1174 | '''替换url过检测''' 1175 | # 较为完整的顶级域名列表(截至目前) 1176 | tlds = [ 1177 | "com", "org", "net", "edu", "gov", "mil", "int", "info", "biz", "name", "museum", "coop", "aero", "pro", "jobs", "mobi", 1178 | "travel", "xxx", "asia", "cat", "tel", "post", "ac", "ad", "ae", "af", "ag", "ai", "al", "am", "an", "ao", "aq", "ar", 1179 | "as", "at", "au", "aw", "ax", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi", "bj", "bm", "bn", "bo", "br", "bs", 1180 | "bt", "bv", "bw", "by", "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl", "cm", "cn", "co", "cr", "cu", "cv", 1181 | "cw", "cx", "cy", "cz", "de", "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "er", "es", "et", "eu", "fi", "fj", "fk", 1182 | "fm", "fo", "fr", "ga", "gb", "gd", "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq", "gr", "gs", "gt", "gu", 1183 | "gw", "gy", "hk", "hm", "hn", "hr", "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir", "is", "it", "je", "jm", 1184 | "jo", "jp", "ke", "kg", "kh", "ki", "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc", "li", "lk", "lr", "ls", 1185 | "lt", "lu", "lv", "ly", "ma", "mc", "md", "me", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq", "mr", "ms", "mt", 1186 | "mu", "mv", "mw", "mx", "my", "mz", "na", "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu", "nz", "om", "pa", 1187 | "pe", "pf", "pg", "ph", "pk", "pl", "pm", "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "rs", "ru", "rw", "sa", 1188 | "sb", "sc", "sd", "se", "sg", "sh", "si", "sj", "sk", "sl", "sm", "sn", "so", "sr", "ss", "st", "su", "sv", "sx", "sy", 1189 | "sz", "tc", "td", "tf", "tg", "th", "tj", "tk", "tl", "tm", "tn", "to", "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", 1190 | "uk", "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu", "wf", "ws", "ye", "yt", "za", "zm", "zw", "academy", 1191 | "accountants", "actor", "adult", "aero", "agency", "airforce", "apartments", "app", "army", "associates", "attorney", 1192 | "auction", "audio", "autos", "band", "bar", "bargains", "beer", "best", "bet", "bid", "bike", "bingo", "bio", "biz", 1193 | "black", "blog", "blue", "bot", "boutique", "build", "builders", "business", "buzz", "cab", "cafe", "camera", "camp", "capital", 1194 | "cards", "care", "career", "careers", "cash", "casino", "catering", "center", "charity", "chat", "cheap", "christmas", 1195 | "church", "city", "claims", "cleaning", "clinic", "clothing", "cloud", "club", "coach", "codes", "coffee", "college", 1196 | "community", "company", "computer", "condos", "construction", "consulting", "contractors", "cooking", "cool", "coop", 1197 | "country", "coupons", "credit", "creditcard", "cricket", "cruises", "dance", "dating", "day", "deals", "degree", 1198 | "delivery", "democrat", "dental", "dentist", "desi", "design", "diamonds", "digital", "direct", "directory", "discount", 1199 | "dog", "domains", "education", "email", "energy", "engineer", "engineering", "enterprises", "equipment", "estate", 1200 | "events", "exchange", "expert", "exposed", "express", "fail", "faith", "family", "fans", "farm", "fashion", "film", 1201 | "finance", "financial", "fish", "fishing", "fitness", "flights", "florist", "flowers", "football", "forsale", "foundation", 1202 | "fun", "fund", "furniture", "futbol", "fyi", "gallery", "games", "gifts", "gives", "glass", "gmbh", "gold", "golf", 1203 | "graphics", "gratis", "green", "gripe", "group", "guide", "guru", "health", "healthcare", "help", "here", "hiphop", 1204 | "hockey", "holdings", "holiday", "home", "homes", "horse", "hospital", "host", "house", "how", "industries", "ink", 1205 | "institute", "insure", "international", "investments", "jewelry", "jobs", "kitchen", "land", "lawyer", "lease", 1206 | "legal", "life", "lighting", "limited", "limo", "link", "live", "loan", "loans", "lol", "love", "ltd", "luxe", "luxury", 1207 | "management", "market", "marketing", "mba", "media", "memorial", "moda", "money", "mortgage", "movie", "museum", 1208 | "name", "navy", "network", "news", "ninja", "now", "online", "ooo", "page", "partners", "parts", "party", "pet", 1209 | "photo", "photography", "photos", "pics", "pictures", "pink", "pizza", "place", "plumbing", "plus", "poker", "press", 1210 | "productions", "properties", "property", "pub", "recipes", "red", "rehab", "reise", "reviews", "rip", "rocks", "run", 1211 | "sale", "salon", "school", "schule", "services", "shoes", "show", "singles", "site", "soccer", "social", "software", 1212 | "solar", "solutions", "space", "studio", "style", "sucks", "supplies", "supply", "support", "surgery", "systems", 1213 | "tattoo", "tax", "taxi", "team", "tech", "technology", "tennis", "theater", "tips", "tires", "today", "tools", "top", 1214 | "tours", "town", "toys", "trade", "training", "travel", "university", "vacations", "vet", "viajes", "video", "villas", 1215 | "vin", "vision", "vodka", "voyage", "watch", "webcam", "website", "wedding", "wiki", "win", "wine", "work", "works", 1216 | "world", "wtf", "zone" 1217 | ] 1218 | 1219 | # 将顶级域名列表转换为正则表达式 1220 | tlds_pattern = '|'.join(tlds) 1221 | # 正则表达式匹配网址,包括不含 http/https 前缀的情况 1222 | url_pattern = re.compile( 1223 | fr'\b((?:https?://)?(?:[a-zA-Z0-9-]+\.)+(?:{tlds_pattern})(?:/[^\s]*)?)\b' 1224 | ) 1225 | 1226 | def replace_dot(match): 1227 | return match.group(0).replace('.', '。') 1228 | # 使用正则表达式进行替换 1229 | return url_pattern.sub(replace_dot, text) 1230 | 1231 | 1232 | async def init_personal_api(chatbot: chatgpt,id: str,personal_name: str,type_from: str): 1233 | person_type = json.loads(personpath.read_text("utf8")) 1234 | if personal_name not in person_type: 1235 | logger.warning(f"默认初始化人格名: {personal_name} 不存在") 1236 | return 1237 | data = MsgData() 1238 | if type_from == "QQguild": 1239 | data = get_c_id(id=id,data=data,c_type='group') 1240 | if data.conversation_id == 'pass': 1241 | logger.info(f"默认人设初始化类型:{type_from},id:{id},检测到疑似重复提示消息,不进行初始化") 1242 | return 1243 | data_tmp = MsgData(conversation_id='pass') 1244 | set_c_id(id,data_tmp,'group') 1245 | else: 1246 | data = get_c_id(id=id,data=data,c_type='group' if 'group' in type_from else 'private') 1247 | if data.conversation_id: 1248 | logger.info(f"默认人设初始化类型:{type_from},id:{id},存在默认会话id:{data.conversation_id},不进行新的初始化") 1249 | return 1250 | data.msg_send = personal_name 1251 | data = await chatbot.init_personality(data) 1252 | 1253 | if not data.msg_recv: 1254 | logger.warning( f"默认人设初始化失败,类型:{type_from},id:{id},错误为:\n{data.error_info}") 1255 | if 'group' in type_from or 'guild' in type_from: 1256 | set_c_id(id,data,'group') 1257 | else: 1258 | set_c_id(id,data,'private') 1259 | --------------------------------------------------------------------------------