├── 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 |

3 |
4 |

5 |
6 |
7 |
8 |
9 | # nonebot-plugin-gpt
10 |
11 | _✨ NoneBot GPT ✨_
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |

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 |
--------------------------------------------------------------------------------