├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── app.py ├── articles.csv ├── commonTools.py ├── config.json ├── configTemplate.json ├── requirements.txt └── wechat.py /.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 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 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 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # for this project 132 | QR.png 133 | itchat.pkl 134 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "PoeAPI"] 2 | path = PoeAPI 3 | url = https://github.com/aspekts/PoeAPI 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 etrobot 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chatgptSummary 2 | 3 | [捕获微信聊天中转发的文章,交给chatgpt生成总结。](https://zhuanlan.zhihu.com/p/611389846) 4 | 5 | ![](https://pic3.zhimg.com/80/v2-bf76c399defe2c82d3ac4d61b8962b4e_720w.webp) 6 | 7 | ### 参考项目: 8 | 9 | [chatgpt-on-wechat](https://github.com/zhayujie/chatgpt-on-wechat) 10 | 11 | ### 使用说明: 12 | 13 | 先安装需要的运行库pip install -r requirements.txt 14 | 15 | 在项目根目录新建文本文件命名为『.env』,填入: 16 | 17 | API_KEY="你的apikey" 18 | 19 | API_BASE_URL="你的base url" 20 | 21 | 运行wechat.py即可 22 | 23 | 24 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from wechat import weChat 2 | 3 | wechat = weChat() 4 | wechat.startup() -------------------------------------------------------------------------------- /articles.csv: -------------------------------------------------------------------------------- 1 | FileName,Url,Summary 2 | WhisperPlus-开源高质量语音转文字技术;谷歌论文阐述Gemini模型研究成果;FontDiffu-开源一键字体生成技术,http://mp.weixin.qq.com/s?__biz=MzI3MzUyNjkyNw==&mid=2247486323&idx=1&sn=6d11683dfd5f867dc20291b2d87c01e4&chksm=eacc83874d5e2563aaea2d7968d1c10c20e700ea0df431442539d20f35b144dc0d81cb5406ff&scene=0&xtrack=1#rd, 3 | "免费的GPT4.0可以用了, 字节除了豆包外的出海应用Coze",https://mp.weixin.qq.com/s/PhoOrWARl6Ru64ne1Ig4dA,"Coze平台是一个专为开发下一代AI聊天机器人而设计的应用编辑平台,提供了丰富的插件工具集、知识库功能、变量功能、定时任务功能、开场对话功能、预览和调试功能等,使用户可以轻松创建、定制和部署AI聊天机器人。用户可以通过Coze实现各种类型的聊天机器人想法,无论是否具备编程经验。此外,用户还可以将创建的机器人发布到不同的社交平台和消息应用上,如Discord和Cici。 4 | 5 | 在Coze上创建聊天机器人的关键步骤包括创建机器人、编写提示、连接所需工具、设置变量、测试和发布。通过优化和完善机器人的开场对话、人物角色和提示,以及利用插件、知识库、变量和定时任务等功能,用户可以为机器人实现更多功能和应用场景。 6 | 7 | 另外,Coze平台还提供了详细的步骤,教导用户如何将机器人发布到Discord和Cici等应用,以便让更多用户与机器人互动。通过遵循这些步骤,用户能够扩大机器人的用户群并提高其知名度。 8 | 9 | 总的来说,Coze平台为用户提供了强大的工具,让他们能够轻松创建、定制和部署AI聊天机器人,为用户提供独特的体验。Coze团队正在不断努力优化平台,以提供更加便捷的使用体验和更多的功能。" 10 | 峰瑞2023年终回顾:风物长宜放眼量,http://mp.weixin.qq.com/s?__biz=MzIzMDAzMTgzOA==&mid=2650858128&idx=1&sn=db502a21c58553e7220b7e74585ef48f&chksm=f34dea21c43a63376b4d6fc3cd1b5ba597155716f90aaa4411b070d9166c333b43b21ee4b6de&mpshare=1&scene=1&srcid=0206gIJWTK5XLJOXPswfcKRu&sharer_shareinfo=8de999d26829467c5b6d66c6b52f13f3&sharer_shareinfo_first=8de999d26829467c5b6d66c6b52f13f3#rd, 11 | 专访椰树:一年播放10亿,我是如何打造爆款直播间?,http://mp.weixin.qq.com/s?__biz=MzAwNzI0MTkwNw==&mid=2661190025&idx=1&sn=3275b28e11ac5e30aafa5532e89e74a4&chksm=806b0055b71c89437da2095283914eb429cbc096c17077981adf89b4a2c9b26bf422ab9bbc0e&mpshare=1&scene=1&srcid=0206kS7VlepI1Q0pqW61kocw&sharer_shareinfo=3f8fa6f53217bbc47cfd50f766ccd54e&sharer_shareinfo_first=3f8fa6f53217bbc47cfd50f766ccd54e#rd, 12 | -------------------------------------------------------------------------------- /commonTools.py: -------------------------------------------------------------------------------- 1 | import json 2 | import string,logging 3 | import re 4 | from concurrent.futures import ThreadPoolExecutor 5 | from http.cookies import SimpleCookie 6 | import pandas as pd 7 | from bs4 import BeautifulSoup 8 | import requests 9 | class posts(): 10 | def __init__(self): 11 | self.filename='./articles.csv' 12 | self.df=pd.read_csv(self.filename,index_col='FileName',keep_default_na=False) 13 | 14 | def update(self,key,field,content): 15 | self.df.at[key, field] = content 16 | self.df.to_csv(self.filename,index_label='FileName') 17 | 18 | class conf(): 19 | def __init__(self): 20 | with open('config.json','rb') as fr: 21 | self.conf = json.loads(fr.read()) 22 | 23 | def get(self,key:str,default=None): 24 | return self.conf.get(key,default) 25 | 26 | def check_prefix(content:str, prefix_list): 27 | for prefix in prefix_list: 28 | if content.startswith(prefix): 29 | return prefix 30 | return None 31 | def extractWxTitle(txt): 32 | pattern = r'\[Link\]\s+(.*?)"\n- - - - - - - - - - - - - - -\n' 33 | if '[链接]' in txt: 34 | pattern = r'\[链接\]+(.*?)」\n- - - - - - - - - - - - - - -\n' 35 | match = re.search(pattern, txt) 36 | if match: 37 | log.debug(match.group(1)) 38 | return match.group(1) 39 | 40 | def dealWxUrl(rawurl:str): 41 | if 'mp.weixin.qq.com' not in rawurl: 42 | return rawurl 43 | cookie = SimpleCookie() 44 | cookie.load(rawurl.split('://')[1][len('mp.weixin.qq.com/s?__'):].replace('&', '')) 45 | cookies = {k: v.value for k, v in cookie.items()} 46 | realurl = "https://mp.weixin.qq.com/s?__biz={biz}&mid={mid}&idx={idx}&sn={sn}".format( 47 | biz=cookies["biz"], 48 | mid=cookies["mid"], 49 | idx=cookies["idx"], 50 | sn=cookies["sn"], 51 | ) 52 | return realurl 53 | 54 | def ripPost(filename,posts): 55 | row = posts.loc[filename] 56 | res = requests.get(row['Url']) 57 | if '23.tv' in row['Url']: 58 | return dealText(ripBili(row['Url'])) 59 | 60 | soup = BeautifulSoup(res.text, "html.parser") 61 | for s in soup(['script', 'style']): 62 | s.decompose() 63 | queryText = soup.get_text(separator="\n") 64 | 65 | if 'mp.weixin.qq.com' in row['Url']: 66 | if conf.get( 'mp.weixin.qq.com' ): 67 | soup=soup.find(id=conf.get( 'mp.weixin.qq.com' )) 68 | query1 = [x.get_text(separator='\n') for x in soup.find_all('section')] 69 | query2 = [x.get_text(separator='\n') for x in soup.find_all('p')] 70 | if len(''.join(query2)) > len(''.join(query1)): 71 | query1 = query2 72 | if len('\n'.join(query1)) == 0: 73 | queryText = re.sub(r'\\x[0-9a-fA-F]{2}', '', 74 | soup.find('meta', {'name': 'description'}).attrs['content']) 75 | else: 76 | query1 = '\n'.join(query1).split('\n') 77 | query = list(set(query1)) 78 | query.sort(key=query1.index) 79 | queryText = '《%s》'%filename+'\n'.join(query) 80 | 81 | return queryText 82 | 83 | def dealText(queryText:str): 84 | if len(queryText) <= 1800: 85 | return queryText 86 | query = queryText.split('\n') 87 | 88 | def checkIndex(text: str): 89 | ch_punc = u"[\u3002\uff1b\uff0c\uff1a\u201c\u201d\uff08\uff09\u3001\uff1f\u300a\u300b]" 90 | if text[0] in '一,二,三,四,五,六,七,八,九' or text[:2] in '首先,其次,再次,然后,最后,结论,总结,综上': 91 | return True 92 | elif text[0].isdigit() and (text[1] in string.punctuation or text[1] in ch_punc): 93 | return True 94 | else: 95 | return False 96 | 97 | keyPoints = [x for x in query if len(x) >= 2 and checkIndex(x)] 98 | keyPointsLen = len('\n'.join(keyPoints)) 99 | query1 = queryText[:3500 - int(keyPointsLen / 2)].split('\n')[:-1] 100 | query1.extend(keyPoints) 101 | query1 = [x for x in query1 if x not in queryText[-3500 + int(keyPointsLen / 2):]] 102 | query1.extend(queryText[-3500 + int(keyPointsLen / 2):].split('\n')[1:]) 103 | query1 = [x.strip() for x in query1 if len(x.strip()) >= 2] 104 | query = list(set(query1)) 105 | query.sort(key=query1.index) 106 | for item in query: 107 | if item in keyPoints: 108 | query[query.index(item)]='-#$RT$#-'+item 109 | queryText = '\n'.join(query).replace('。\n', '-#$RT$#-').replace('\n', '').replace('-#$RT$#-', '\n') 110 | log.debug('Text Length: %s' % len(queryText)) 111 | return queryText 112 | 113 | 114 | def ripBili(bvUrl): 115 | def bili_player_list(bvid): 116 | url = 'https://api.bilibili.com/x/player/pagelist?bvid=' + bvid 117 | response = requests.get(url) 118 | cid_list = [x['cid'] for x in response.json()['data']] 119 | return cid_list 120 | 121 | def bili_subtitle_list(bvid, cid): 122 | url = f'https://api.bilibili.com/x/player/v2?bvid={bvid}&cid={cid}' 123 | sessdata=conf.get('SESSDATA') 124 | if conf.get('vika.cn'): 125 | vikaUrl = 'https://api.vika.cn/fusion/v1/datasheets/dstMiuU9zzihy1LzFX/records?viewId=viwoAJhnS2NMT&fieldKey=name' 126 | vikaCache = json.loads(requests.get(vikaUrl, headers={'Authorization': conf.get("vika.cn")}).text)['data']['records'] 127 | sessdata=[x['fields']['value'] for x in vikaCache if x['recordId']=='recRh258ujPiq'][0] 128 | response = requests.get(url, cookies={'SESSDATA':sessdata }) 129 | subtitles = response.json()['data']['subtitle']['subtitles'] 130 | if subtitles: 131 | return ['https:' + x['subtitle_url'] for x in subtitles] 132 | else: 133 | return [] 134 | 135 | def bili_subtitle(bvid, cid): 136 | subtitles = bili_subtitle_list(bvid, cid) 137 | if subtitles: 138 | response = requests.get(subtitles[0]) 139 | if response.status_code == 200: 140 | body = response.json()['body'] 141 | return body 142 | return [] 143 | 144 | soup = BeautifulSoup(requests.get(bvUrl).text, 'html.parser') 145 | bvid = soup.find('meta', {'itemprop': 'url'})['content'].split('/')[-2] 146 | query = bili_subtitle(bvid, bili_player_list(bvid)[0]) 147 | 148 | return '\n'.join(x['content'] for x in query) 149 | 150 | def is_contain_chinese(check_str): 151 | for ch in check_str: 152 | if u'\u4e00' <= ch <= u'\u9fff': 153 | return True 154 | return False 155 | 156 | thread_pool = ThreadPoolExecutor(max_workers=8) 157 | log = logging.getLogger(__name__) 158 | log.setLevel(logging.DEBUG) 159 | posts=posts() 160 | conf=conf() -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "proxy":"http://127.0.0.1:7890", 3 | "single_chat_prefix": ["Bot", "@Vega"], 4 | "single_chat_reply_prefix": "[KiMi] ", 5 | "group_name_white_list": ["AI 应用研讨群","\uD83E\uDDE1 \uD83C\uDF84即刻Coze\uD83C\uDF84 \uD83E\uDDE1"], 6 | "mp.weixin.qq.com": "js_content", 7 | "model": "moonshot-v1-8k" 8 | } -------------------------------------------------------------------------------- /configTemplate.json: -------------------------------------------------------------------------------- 1 | { 2 | "proxy":"http://127.0.0.1:7890", 3 | "single_chat_prefix": ["Bot", "@Vega"], 4 | "group_name_white_list": ["AI 应用研讨群","猴塞雷666","产品小分队"], 5 | "mp.weixin.qq.com": "js_content", 6 | "llm": "a2", 7 | "Cookie":"" 8 | } -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4 2 | itchat-uos==1.5.0.dev0 3 | pandas 4 | requests 5 | litellm 6 | python-dotenv -------------------------------------------------------------------------------- /wechat.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | import logging 3 | 4 | import itchat 5 | from itchat.content import * 6 | import pandas as pd 7 | import commonTools as tl 8 | import os 9 | from openai import OpenAI 10 | from dotenv import load_dotenv,find_dotenv 11 | load_dotenv(find_dotenv()) 12 | 13 | client = OpenAI( 14 | api_key= os.environ["API_KEY"], 15 | base_url=os.environ["API_BASE_URL"], 16 | ) 17 | @itchat.msg_register([TEXT,SHARING]) 18 | def handler_single_msg(msg): 19 | weChat().handle(msg) 20 | return None 21 | 22 | 23 | @itchat.msg_register([TEXT,SHARING], isGroupChat=True) 24 | def handler_group_msg(msg): 25 | weChat().handle_group(msg) 26 | return None 27 | 28 | 29 | class weChat(): 30 | def __init__(self): 31 | pass 32 | 33 | def startup(self): 34 | # login by scan QRCode 35 | itchat.auto_login(hotReload=True) 36 | # start message listener 37 | itchat.run() 38 | 39 | def msg49(self,msg:dict): 40 | if msg['FileName'] not in tl.posts.df.index: 41 | df = pd.DataFrame(data=[[msg['Url'], '']], index=[msg['FileName']], 42 | columns=['Url', 'Summary']) 43 | tl.posts.df = pd.concat([tl.posts.df,df]) 44 | tl.posts.df.to_csv(tl.posts.filename, index_label='FileName') 45 | 46 | def handle(self, msg): 47 | from_user_id = msg['FromUserName'] 48 | to_user_id = msg['ToUserName'] # 接收人id 49 | other_user_id = msg['User']['UserName'] # 对手方id 50 | content = msg['Text'] 51 | quote='\n- - - - - - - - - - - - - - -\n' 52 | if from_user_id == other_user_id: 53 | match_prefix = tl.check_prefix(content,tl.conf.get('single_chat_prefix')) 54 | if match_prefix: 55 | content=content[len(match_prefix):] 56 | query='' 57 | prompt='' 58 | filename = '' 59 | if msg['MsgType'] == 49: 60 | self.msg49(msg) 61 | filename = msg['FileName'] 62 | prompt = '用中文总结要点,带序号:' 63 | query = tl.ripPost(filename, tl.posts.df) 64 | elif '[Link]' in content or '[链接]' in content: 65 | filename = tl.extractWxTitle(content) 66 | prompt = content.split(quote)[-1] 67 | query= tl.ripPost(filename, tl.posts.df) 68 | elif quote in content : 69 | querys=content.split(quote) 70 | query=querys[0] 71 | prompt=querys[1] 72 | elif match_prefix is not None: 73 | prompt = content 74 | tl.thread_pool.submit(self._do_send, query,from_user_id,prompt,filename) 75 | 76 | 77 | def handle_group(self, msg): 78 | group_name = msg['User'].get('NickName', None) 79 | if not group_name: 80 | return "" 81 | if not (group_name in tl.conf.get('group_name_white_list') or 'ALL_GROUP' in tl.conf.get( 82 | 'group_name_white_list')): 83 | return "" 84 | if msg['MsgType']==49: 85 | self.msg49(msg) 86 | return 87 | if '[Message cannot be displayed]' in msg['Content']: 88 | query=tl.dealText(tl.ripBili(tl.posts.df[tl.posts.df['Url'].str.contains('23.tv')]['Url'].iloc[-1])) 89 | tl.thread_pool.submit(self._do_send_group, query, msg, msg['FileName'], '用中文总结要点,带序号:') 90 | return 91 | 92 | quote = '\n- - - - - - - - - - - - - - -\n' 93 | if not msg['IsAt']: 94 | return 95 | content = msg['Content'].split(quote) 96 | name=msg['User']['Self']['DisplayName'] 97 | if name == '': 98 | name=msg['User']['Self']['NickName'] 99 | prompt = content[-1][len(name)+1:] 100 | query = content[0][len(msg['ActualNickName'])+1:] 101 | title='' 102 | if '[Link]' in msg['Content'] or '[链接]' in msg['Content']: 103 | title = tl.extractWxTitle(msg['Content']) 104 | query= tl.ripPost(title,tl.posts.df) 105 | if query is not None: 106 | tl.thread_pool.submit(self._do_send_group,query,msg,title,prompt) 107 | 108 | def send(self, msg, receiver): 109 | itchat.send(msg, toUserName=receiver) 110 | 111 | def _do_send(self, query,reply_user_id,prompt,title): 112 | try: 113 | if query=='' and prompt=='': 114 | return 115 | context = dict() 116 | context['from_user_id'] = reply_user_id 117 | queryText = tl.conf.get("character_desc", "") + prompt 118 | if query!='': 119 | queryText=queryText+'\n『%s\n』'%query 120 | queryText = queryText +'\nTL;DR; reply in Chinese.' 121 | completion = client.chat.completions.create( 122 | model=tl.conf.get("model"), 123 | messages=[ 124 | # {"role": "system", "content": "你是Moonshot AI研发的智能助理kimi"}, 125 | {"role": "user", "content": queryText} 126 | ], 127 | temperature=0.7, 128 | ) 129 | reply_text= tl.conf.get('single_chat_reply_prefix') + completion.choices[0].message.content 130 | if reply_text is not None: 131 | self.send(reply_text, reply_user_id) 132 | if title != '' and title in tl.posts.df.index and tl.is_contain_chinese( 133 | reply_text) and reply_text.startswith('[Poe]'): 134 | tl.posts.update(key=title, field='Summary', content=reply_text[len('[Poe]'):]) 135 | 136 | except Exception as e: 137 | tl.log.exception(e) 138 | 139 | def _do_send_group(self,query,msg,title,prompt): 140 | if not query: 141 | return 142 | try: 143 | queryDF = tl.posts.df.loc[title]['Summary'] 144 | if len(queryDF)>300: 145 | self.send(queryDF, msg['User']['UserName']) 146 | return 147 | except Exception as e: 148 | logging.error(e) 149 | context = dict() 150 | context['from_user_id'] = msg['ActualUserName'] 151 | group_id = msg['User']['UserName'] 152 | query = prompt + '\n『%s』'%query 153 | completion = client.chat.completions.create( 154 | model=tl.conf.get("model", ""), 155 | messages=[ 156 | # {"role": "system", "content": "你是Moonshot AI研发的智能助理kimi"}, 157 | {"role": "user", "content": query} 158 | ], 159 | temperature=0.7, 160 | ) 161 | 162 | reply_text = completion.choices[0].message.content 163 | if reply_text is not None: 164 | self.send('@' + msg['ActualNickName'] + ' ' + reply_text.strip(), group_id) 165 | if title != '' and title in tl.posts.df.index and tl.is_contain_chinese(reply_text): 166 | tl.posts.update(key=title,field= 'Summary',content=reply_text) 167 | 168 | --------------------------------------------------------------------------------