Skip to content

Commit

Permalink
init: add wechat and wechat_mp channel
Browse files Browse the repository at this point in the history
  • Loading branch information
zhayujie committed Feb 13, 2023
0 parents commit b13627a
Show file tree
Hide file tree
Showing 18 changed files with 664 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.DS_Store
.idea
__pycache__/
venv*
*.pyc
config.json
QR.png
19 changes: 19 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Copyright (c) 2023 zhayujie

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

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

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# 简介

**AI模型** 接入各类 **消息应用**,开发者通过轻量配置即可在二者之间选择一条连线,运行起一个智能对话机器人,在一个项目中轻松完成多条链路的切换。该架构扩展性强,每接入一个应用可复用已有的算法能力,同样每接入一个模型也可作用于所有应用之上。

**模型:**

- [x] ChatGPT
- ...

**应用:**

- [ ] 终端
- [ ] Web
- [x] 个人微信
- [x] 公众号
- [ ] 企业微信
- [ ] Telegram
- [ ] QQ
- [ ] 钉钉
- ...



# 快速开始

## 一、准备

### 1.运行环境

支持 Linux、MacOS、Windows 系统(Linux服务器上可长期运行)。同时需安装 Python,建议Python版本在 3.7.1~3.10 之间。

项目代码克隆:

```bash
git clone https://github.com/zhayujie/bot-on-anything
cd bot-on-anything/
```
> 或在 Realase 直接手动下载源码。
### 2.配置说明

核心配置文件为 `config.json`



## 二、选择模型

### 1.ChatGPT


## 三、选择应用

### 1.微信


### 2.公众号




## 四、运行

21 changes: 21 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# encoding:utf-8

import config
from channel import channel_factory
from common.log import logger


if __name__ == '__main__':
try:
# load config
config.load_config()
logger.info("[INIT] load config: {}".format(config.conf()))

# create channel
channel = channel_factory.create_channel(config.conf().get("channel"))

# startup channel
channel.startup()
except Exception as e:
logger.error("App startup failed!")
logger.exception(e)
9 changes: 9 additions & 0 deletions bridge/bridge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from model import model_factory
import config

class Bridge(object):
def __init__(self):
pass

def fetch_reply_content(self, query, context):
return model_factory.create_bot(config.conf().get("model")).reply(query, context)
31 changes: 31 additions & 0 deletions channel/channel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""
Message sending channel abstract class
"""

from bridge.bridge import Bridge

class Channel(object):
def startup(self):
"""
init channel
"""
raise NotImplementedError

def handle(self, msg):
"""
process received msg
:param msg: message object
"""
raise NotImplementedError

def send(self, msg, receiver):
"""
send message to user
:param msg: message content
:param receiver: receiver channel account
:return:
"""
raise NotImplementedError

def build_reply_content(self, query, context=None):
return Bridge().fetch_reply_content(query, context)
21 changes: 21 additions & 0 deletions channel/channel_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""
channel factory
"""
from common import const

def create_channel(channel_type):
"""
create a channel instance
:param channel_type: channel type code
:return: channel instance
"""
if channel_type == const.WECHAT:
from channel.wechat.wechat_channel import WechatChannel
return WechatChannel()

elif channel_type == const.WECHAT_MP:
from channel.wechat.wechat_mp_channel import WechatPublicAccount
return WechatPublicAccount()

else:
raise RuntimeError
Empty file.
165 changes: 165 additions & 0 deletions channel/wechat/wechat_channel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# encoding:utf-8

"""
wechat channel
"""
import itchat
import json
from itchat.content import *
from channel.channel import Channel
from concurrent.futures import ThreadPoolExecutor
from common.log import logger
from config import conf
import requests
import io

thread_pool = ThreadPoolExecutor(max_workers=8)


@itchat.msg_register(TEXT)
def handler_single_msg(msg):
WechatChannel().handle(msg)
return None


@itchat.msg_register(TEXT, isGroupChat=True)
def handler_group_msg(msg):
WechatChannel().handle_group(msg)
return None


class WechatChannel(Channel):
def __init__(self):
pass

def startup(self):
# login by scan QRCode
itchat.auto_login(enableCmdQR=2)

# start message listener
itchat.run()

def handle(self, msg):
logger.debug("[WX]receive msg: " + json.dumps(msg, ensure_ascii=False))
from_user_id = msg['FromUserName']
to_user_id = msg['ToUserName'] # 接收人id
other_user_id = msg['User']['UserName'] # 对手方id
content = msg['Text']
match_prefix = self.check_prefix(content, conf().get('single_chat_prefix'))
if from_user_id == other_user_id and match_prefix is not None:
# 好友向自己发送消息
if match_prefix != '':
str_list = content.split(match_prefix, 1)
if len(str_list) == 2:
content = str_list[1].strip()

img_match_prefix = self.check_prefix(content, conf().get('image_create_prefix'))
if img_match_prefix:
content = content.split(img_match_prefix, 1)[1].strip()
thread_pool.submit(self._do_send_img, content, from_user_id)
else:
thread_pool.submit(self._do_send, content, from_user_id)

elif to_user_id == other_user_id and match_prefix:
# 自己给好友发送消息
str_list = content.split(match_prefix, 1)
if len(str_list) == 2:
content = str_list[1].strip()
img_match_prefix = self.check_prefix(content, conf().get('image_create_prefix'))
if img_match_prefix:
content = content.split(img_match_prefix, 1)[1].strip()
thread_pool.submit(self._do_send_img, content, to_user_id)
else:
thread_pool.submit(self._do_send, content, to_user_id)


def handle_group(self, msg):
logger.debug("[WX]receive group msg: " + json.dumps(msg, ensure_ascii=False))
group_name = msg['User'].get('NickName', None)
group_id = msg['User'].get('UserName', None)
if not group_name:
return ""
origin_content = msg['Content']
content = msg['Content']
content_list = content.split(' ', 1)
context_special_list = content.split('\u2005', 1)
if len(context_special_list) == 2:
content = context_special_list[1]
elif len(content_list) == 2:
content = content_list[1]

config = conf()
match_prefix = (msg['IsAt'] and not config.get("group_at_off", False)) or self.check_prefix(origin_content, config.get('group_chat_prefix')) \
or self.check_contain(origin_content, config.get('group_chat_keyword'))
if ('ALL_GROUP' in config.get('group_name_white_list') or group_name in config.get('group_name_white_list') or self.check_contain(group_name, config.get('group_name_keyword_white_list'))) and match_prefix:
img_match_prefix = self.check_prefix(content, conf().get('image_create_prefix'))
if img_match_prefix:
content = content.split(img_match_prefix, 1)[1].strip()
thread_pool.submit(self._do_send_img, content, group_id)
else:
thread_pool.submit(self._do_send_group, content, msg)

def send(self, msg, receiver):
logger.info('[WX] sendMsg={}, receiver={}'.format(msg, receiver))
itchat.send(msg, toUserName=receiver)

def _do_send(self, query, reply_user_id):
try:
if not query:
return
context = dict()
context['from_user_id'] = reply_user_id
reply_text = super().build_reply_content(query, context)
if reply_text:
self.send(conf().get("single_chat_reply_prefix") + reply_text, reply_user_id)
except Exception as e:
logger.exception(e)

def _do_send_img(self, query, reply_user_id):
try:
if not query:
return
context = dict()
context['type'] = 'IMAGE_CREATE'
img_url = super().build_reply_content(query, context)
if not img_url:
return

# 图片下载
pic_res = requests.get(img_url, stream=True)
image_storage = io.BytesIO()
for block in pic_res.iter_content(1024):
image_storage.write(block)
image_storage.seek(0)

# 图片发送
logger.info('[WX] sendImage, receiver={}'.format(reply_user_id))
itchat.send_image(image_storage, reply_user_id)
except Exception as e:
logger.exception(e)

def _do_send_group(self, query, msg):
if not query:
return
context = dict()
context['from_user_id'] = msg['ActualUserName']
reply_text = super().build_reply_content(query, context)
if reply_text:
reply_text = '@' + msg['ActualNickName'] + ' ' + reply_text.strip()
self.send(conf().get("group_chat_reply_prefix", "") + reply_text, msg['User']['UserName'])


def check_prefix(self, content, prefix_list):
for prefix in prefix_list:
if content.startswith(prefix):
return prefix
return None


def check_contain(self, content, keyword_list):
if not keyword_list:
return None
for ky in keyword_list:
if content.find(ky) != -1:
return True
return None
Loading

0 comments on commit b13627a

Please sign in to comment.