initial commit

This commit is contained in:
linghaihui 2023-03-22 15:25:55 +08:00
commit f173af8104
46 changed files with 1562 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
venv/
__pycache__/
cookie*.json
env
config.json

1
README.md Normal file
View File

@ -0,0 +1 @@
本项目实现了[New Bing 聊天的接口](./new-bing),并做了一个简单的[小程序](./bingchat),同时将`chatgpt 3.5`和`New Bing`集成到[微信](./wechatbot)。

3
bingchat/README.md Normal file
View File

@ -0,0 +1,3 @@
# New Bing Bot
<image style="width: 320px" src="./snapshoot.png"/>

35
bingchat/app.js Normal file
View File

@ -0,0 +1,35 @@
import SERVER_HOST from '../../config';
App({
onShow: function () {},
onLaunch: function () {
this.getSid();
},
globalData: {},
getSid: function () {
var that = this;
if (!this.globalData.sid) {
var sid = wx.getStorageSync('sid1');
if (!sid) {
wx.login({
success: (res) => {
wx.request({
url: SERVER_HOST + '/bing/openid',
data: {
code: res.code,
},
success: (res) => {
that.globalData.sid = res.data.data.openid;
wx.setStorageSync('sid1', that.globalData.sid);
},
});
},
});
} else {
this.globalData.sid = sid;
wx.setStorageSync('sid1', this.globalData.sid);
}
}
return this.globalData.sid ? this.globalData.sid : '';
},
});

21
bingchat/app.json Normal file
View File

@ -0,0 +1,21 @@
{
"pages": [
"pages/index/index"
],
"useExtendedLib": {
"weui":true
},
"usingComponents": {
"chat-box":"components/chatbox/index"
},
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTitleText": "New Bing Bot 🤖",
"navigationBarTextStyle": "black"
},
"sitemapLocation": "sitemap.json",
"networkTimeout": {
"request": 1800000
}
}

4
bingchat/app.wxss Normal file
View File

@ -0,0 +1,4 @@
page {
background-color: #fff;
font-size: 28rpx;
}

View File

@ -0,0 +1,94 @@
const app = getApp();
Component({
options: {
addGlobalClass: true,
multipleSlots: true,
},
properties: {},
pageLifetimes: {
show: function () {
this.initMessageHistory();
},
},
lifetimes: {
attached() {
var that = this;
app.globalData.cht = that;
//that.initMessageHistory();
wx.getSystemInfo({
success: function (res) {
that.setData({
systemInfo: res,
});
},
});
},
detached() {
try {
} catch (error) {}
},
},
data: {
chatList: [],
},
methods: {
initMessageHistory() {
var that = this;
var data = wx.getStorageSync("chatList");
data = data ? data : [];
data.forEach((v) => {
if (v["suggests"] === undefined) {
v["suggests"] = [];
}
});
if (data.length > 0) {
that.setData({
chatList: data,
});
}
},
clearChat: function (e) {
var that = this;
var index = e.currentTarget.dataset.index;
var data = this.data.chatList;
wx.showModal({
content: "是否删除该条记录?",
complete: (res) => {
if (res.confirm) {
data.splice(index, 1);
that.setData({
chatList: data,
});
wx.setStorage({
key: "chatList",
data: data,
});
}
},
});
},
copyContent: function (e) {
var index = e.currentTarget.dataset.index;
var content = this.data.chatList[index].originContent;
wx.setClipboardData({
data: content,
success: function () {
wx.showToast({
title: "复制成功",
});
},
});
},
suggestSubmit: function (e) {
var suggest = e.currentTarget.dataset.suggest;
this.triggerEvent(
"suggestSubmit",
{
suggest,
},
{}
);
},
},
});

View File

@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@ -0,0 +1,22 @@
<view wx:if="{{chatList.length == 0}}" style="text-align:center;color: rgb(180, 187, 196);font-size: 28rpx;">输入问题开始和New Bing聊天吧~</view>
<scroll-view class="chat" scroll-y="true" scroll-into-view="{{scrollId}}" style="height:{{systemInfo.windowHeight - 70}}px;" enable-back-to-top="{{true}}" scroll-anchoring="{{true}}" enhanced="{{true}}" enable-flex="{{true}}">
<view wx:for="{{chatList}}" wx:key="index" wx:for-item="item" id="{{'item'+index}}">
<view class="chat-item left" wx:if="{{item.type != 'man'}}" id="msg-{{index}}">
<image class="avatar" src="{{item.avatarUrl}}" style="display: flex;" bindlongpress="clearChat" data-index="{{index}}"></image>
<view class="chat-box" style="margin-left: 20rpx;">
<text class="dt">{{item.dt}}</text>
<view class="content bg-white" bindlongpress="copyContent" data-index="{{index}}"><view class="{{item.blink ? 'blinking': ''}}">{{item.originContent}}</view></view>
<view class="suggest" >
<text class="suggest-item" bindtap="suggestSubmit" data-suggest="{{suggest}}" wx:for="{{item.suggests}}" wx:for-item="suggest">{{suggest}}</text>
</view>
</view>
</view>
<view class="chat-item right" wx:if="{{item.type == 'man' }}" id="msg-{{index}}">
<view class="chat-box" style="margin-right: 20rpx;">
<text class="dt" style="display: block;text-align: right">{{item.dt}}</text>
<view class="content bg-green" bindlongpress="copyContent" data-index="{{index}}">{{item.originContent}}</view>
</view>
<image class="avatar" src="{{item.avatarUrl}}" bindlongpress="clearChat" data-index="{{index}}"></image>
</view>
</view>
</scroll-view>

View File

@ -0,0 +1,77 @@
.chat {
display: flex;
flex-direction: column;
}
.chat-item {
display: flex;
padding: 10rpx 10rpx 35rpx;
flex-direction: row;
}
.left {
justify-content: flex-start;
}
.right {
justify-content: flex-end;
}
.avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background-color: rgb(224, 226, 232)
}
.dt {
font-size: 24rpx;
color: rgb(180, 187, 196)
}
.content {
border-radius: 16rpx;
padding: 10rpx 16rpx 10rpx 16rpx;
word-break: break-all;
white-space: pre-wrap;
}
.bg-green {
background-color: #d2f9d1;
}
.bg-white {
background-color: #f4f6f8;
}
.chat-box {
display: flex;
flex-direction: column;
max-width: 84%;
}
.suggest {
display: flex;
justify-content: flex-start;
margin-top: 8rpx;
flex-wrap: wrap;
}
.suggest-item{
background-color: #f4f6f8;
display: inline-block;
padding: 5rpx 10rpx 5rpx 10rpx;
border-radius: 10rpx;
margin-right: 16rpx;
color: rgb(180, 187, 196);
margin-bottom: 10rpx;
font-size: 25rpx;
}
@keyframes blink {
from { display: 1.0; }
to { opacity: 0.0; }
}
.blinking {
animation: blink 1.5s ease-in-out infinite;
display: block;
}

1
bingchat/config.js Normal file
View File

@ -0,0 +1 @@
export default SERVER_HOST = 'https://example.com';

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
bingchat/image/person.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -0,0 +1,222 @@
import SERVER_HOST from "../../config";
Date.prototype.format = function (fmt) {
var o = {
"M+": this.getMonth() + 1, //月份
"d+": this.getDate(), //日
"h+": this.getHours(), //小时
"m+": this.getMinutes(), //分
"s+": this.getSeconds(), //秒
"q+": Math.floor((this.getMonth() + 3) / 3), //季度
S: this.getMilliseconds(), //毫秒
};
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(
RegExp.$1,
(this.getFullYear() + "").substr(4 - RegExp.$1.length)
);
}
for (var k in o) {
if (new RegExp("(" + k + ")").test(fmt)) {
fmt = fmt.replace(
RegExp.$1,
RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length)
);
}
}
return fmt;
};
function getNow() {
return new Date().format("yyyy-MM-dd hh:mm:ss");
}
const app = getApp();
const robAvatar = "../../image/bing-avatar.png";
const personAvatar = "../../image/person.jpeg";
Page({
data: {
InputBottom: 24,
content: "",
},
InputFocus(e) {
this.setData({
InputBottom: e.detail.height + 2,
});
},
InputBlur(e) {
this.setData({
InputBottom: 24,
});
},
processContent(content) {
return content.replace(/\\n/g, "\n");
},
resetConversation: function () {
wx.request({
url: SERVER_HOST + "/bing/reset",
data: {
t: new Date().getTime(),
sid: app.getSid(),
},
enableHttp2: true,
});
},
onShow() {
const cht = app.globalData.cht;
if (cht.data.chatList.length > 1) {
cht.setData({
scrollId: "item" + (cht.data.chatList.length - 2),
});
}
},
submitContent: function (content) {
var that = this;
const cht = app.globalData.cht;
content = this.processContent(content);
cht.data.chatList.push({
type: "man",
avatarUrl: personAvatar,
dt: getNow(),
originContent: content,
});
cht.setData({
chatList: cht.data.chatList,
});
that.setData({
content: "",
});
if (content == "重新对话!") {
this.resetConversation();
cht.data.chatList.push({
type: "rob",
avatarUrl: robAvatar,
dt: getNow(),
originContent: "现在我们可以开始新的对话😊",
suggests: [],
});
cht.setData({
chatList: cht.data.chatList,
});
that.scrollTo(cht);
return;
} else {
cht.data.chatList.push({
type: "rob",
avatarUrl: robAvatar,
dt: getNow(),
originContent: "搜索中🔍...",
suggests: [],
blink: true,
});
cht.setData({
chatList: cht.data.chatList,
});
}
wx.request({
url: SERVER_HOST + "/bing/chat",
method: "GET",
data: {
q: content,
t: new Date().getTime(),
sid: app.getSid(),
},
enableHttp2: true,
success(res) {
try {
var robContent = "";
var suggests = [];
if (res.statusCode != 200) {
robContent =
"抱歉😭,网络异常,请稍后重试 [" + res.statusCode + "]";
suggests.push(content);
} else {
robContent = res.data["data"]["status"];
if (robContent == "Success") {
robContent = res.data["data"]["text"];
suggests = res.data["data"]["suggests"];
if (robContent.indexOf("New topic") != -1) {
robContent += "\n发送“重新对话”开始新的对话";
suggests.push("重新对话!");
suggests.push(content);
}
} else {
if (robContent == "Throttled") {
robContent = "这真是愉快,但你已达到每日限制。是否明天再聊?";
suggests.push("重新对话!");
suggests.push(content);
} else {
var msg = res.data["data"]["message"];
if (msg.indexOf("has expired") != -1) {
that.resetConversation();
robContent = "本轮对话已过期,请重新开始。";
suggests.push(content);
} else {
robContent = "抱歉😭,发生错误:" + msg + ",请重试";
suggests.push(content);
}
}
}
}
cht.data.chatList.pop();
cht.data.chatList.push({
type: "rob",
avatarUrl: robAvatar,
dt: getNow(),
originContent: that.processContent(robContent),
suggests: suggests,
});
cht.setData({
chatList: cht.data.chatList,
});
wx.setStorage({
key: "chatList",
data: cht.data.chatList,
});
} catch (error) {
console.log(error);
wx.showToast({
title: "fatal error",
});
}
},
fail(e) {
console.log(e);
cht.data.chatList.pop();
cht.data.chatList.push({
type: "rob",
avatarUrl: robAvatar,
dt: getNow(),
originContent: e.errMsg,
suggests: [],
});
},
});
that.scrollTo(cht);
},
scrollTo: function (cht) {
setTimeout(() => {
cht.setData({
scrollId: "item" + (cht.data.chatList.length - 1),
});
}, 50);
},
submit() {
var content = this.data.content;
if (content.length == 0 || content == null || content.trim().length == 0) {
return;
}
this.submitContent(content);
},
onShareAppMessage() {
return {
title: "New Bing Bot 🤖",
path: "/pages/index/index",
};
},
onSuggestSubmit: function (e) {
var suggest = e.detail.suggest;
this.submitContent(suggest);
},
});

View File

@ -0,0 +1,4 @@
{
"usingComponents": {
}
}

View File

@ -0,0 +1,4 @@
<chat-box bindsuggestSubmit="onSuggestSubmit"></chat-box>
<view style="bottom:{{InputBottom}}px; border-radius: 20rpx;margin-left: 1.5%;width: 97%;min-height: 88rpx;position: fixed;background-color: #f4f6f8;">
<textarea bindfocus="InputFocus" bindblur="InputBlur" model:value="{{content}}" adjust-position="{{false}}" focus="{{false}}" maxlength="2000" auto-height="{{true}}" cursor-spacing="10" bindconfirm="submit" fixed="{{true}}" show-confirm-bar="{{false}}" confirm-type="send" placeholder="请输入问题..." style="padding: 10rpx;width: 98%;" disable-default-padding="{{true}}"></textarea>
</view>

View File

View File

@ -0,0 +1,27 @@
{
"appid": "wxee7496be5b68b740",
"compileType": "miniprogram",
"libVersion": "2.12.3",
"packOptions": {
"ignore": [],
"include": []
},
"setting": {
"es6": true,
"postcss": true,
"minified": true,
"enhance": true,
"packNpmRelationList": [],
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
},
"uglifyFileName": false
},
"condition": {},
"editorSetting": {
"tabIndent": "tab",
"tabSize": 2
}
}

View File

@ -0,0 +1,9 @@
{
"description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
"projectname": "bingchat",
"setting": {
"compileHotReLoad": true,
"urlCheck": false
},
"libVersion": "2.15.0"
}

7
bingchat/sitemap.json Normal file
View File

@ -0,0 +1,7 @@
{
"desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
"rules": [{
"action": "disallow",
"page": "*"
}]
}

BIN
bingchat/snapshoot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

13
new-bing/Dockerfile Normal file
View File

@ -0,0 +1,13 @@
FROM sanicframework/sanic:3.11-latest
WORKDIR /sanic
COPY app.py EdgeGPT.py cookie.json cookie1.json cookie2.json requirements.txt /sanic/
RUN pip install -r requirements.txt
RUN rm requirements.txt
EXPOSE 8000
CMD ["python", "app.py"]

336
new-bing/EdgeGPT.py Normal file
View File

@ -0,0 +1,336 @@
"""
Main.py
"""
from __future__ import annotations
import asyncio
import json
import os
import random
import ssl
import uuid
from enum import Enum
from typing import Generator
from typing import Literal
from typing import Optional
from typing import Union
import certifi
import httpx
import websockets.client as websockets
DELIMITER = "\x1e"
# Generate random IP between range 13.104.0.0/14
FORWARDED_IP = (f"13.{random.randint(104, 107)}.{random.randint(0, 255)}.{random.randint(0, 255)}")
HEADERS = {
"accept": "application/json",
"accept-language": "en-US,en;q=0.9",
"content-type": "application/json",
"sec-ch-ua": '"Not_A Brand";v="99", "Microsoft Edge";v="110", "Chromium";v="110"',
"sec-ch-ua-arch": '"x86"',
"sec-ch-ua-bitness": '"64"',
"sec-ch-ua-full-version": '"109.0.1518.78"',
"sec-ch-ua-full-version-list": '"Chromium";v="110.0.5481.192", "Not A(Brand";v="24.0.0.0", "Microsoft Edge";v="110.0.1587.69"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-model": "",
"sec-ch-ua-platform": '"Windows"',
"sec-ch-ua-platform-version": '"15.0.0"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"x-ms-client-request-id": str(uuid.uuid4()),
"x-ms-useragent": "azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.10.0 OS/Win32",
"Referer": "https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx",
"Referrer-Policy": "origin-when-cross-origin",
"x-forwarded-for": FORWARDED_IP,
}
HEADERS_INIT_CONVER = {
"authority": "edgeservices.bing.com",
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"accept-language": "en-US,en;q=0.9",
"cache-control": "max-age=0",
"sec-ch-ua": '"Chromium";v="110", "Not A(Brand";v="24", "Microsoft Edge";v="110"',
"sec-ch-ua-arch": '"x86"',
"sec-ch-ua-bitness": '"64"',
"sec-ch-ua-full-version": '"110.0.1587.69"',
"sec-ch-ua-full-version-list": '"Chromium";v="110.0.5481.192", "Not A(Brand";v="24.0.0.0", "Microsoft Edge";v="110.0.1587.69"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-model": '""',
"sec-ch-ua-platform": '"Windows"',
"sec-ch-ua-platform-version": '"15.0.0"',
"sec-fetch-dest": "document",
"sec-fetch-mode": "navigate",
"sec-fetch-site": "none",
"sec-fetch-user": "?1",
"upgrade-insecure-requests": "1",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.69",
"x-edge-shopping-flag": "1",
}
ssl_context = ssl.create_default_context()
ssl_context.load_verify_locations(certifi.where())
class NotAllowedToAccess(Exception):
pass
class ConversationStyle(Enum):
creative = "h3relaxedimg"
balanced = "galileo"
precise = "h3precise"
CONVERSATION_STYLE_TYPE = Optional[Union[ConversationStyle, Literal["creative", "balanced", "precise"]]]
def append_identifier(msg: dict) -> str:
"""
Appends special character to end of message to identify end of message
"""
# Convert dict to json string
return json.dumps(msg) + DELIMITER
class ChatHubRequest:
"""
Request object for ChatHub
"""
def __init__(
self,
conversation_signature: str,
client_id: str,
conversation_id: str,
invocation_id: int = 0,
) -> None:
self.struct: dict = {}
self.client_id: str = client_id
self.conversation_id: str = conversation_id
self.conversation_signature: str = conversation_signature
self.invocation_id: int = invocation_id
def update(
self,
prompt: str,
conversation_style: CONVERSATION_STYLE_TYPE,
options: list | None = None,
) -> None:
"""
Updates request object
"""
if options is None:
options = [
"deepleo",
"enable_debug_commands",
"disable_emoji_spoken_text",
"enablemm",
]
if conversation_style:
if not isinstance(conversation_style, ConversationStyle):
conversation_style = getattr(ConversationStyle, conversation_style)
options = [
"deepleo",
"enable_debug_commands",
"disable_emoji_spoken_text",
"enablemm",
conversation_style.value,
]
self.struct = {
"arguments": [
{
"source": "cib",
"optionsSets": options,
"isStartOfSession": self.invocation_id == 0,
"message": {
"author": "user",
"inputMethod": "Keyboard",
"text": prompt,
"messageType": "Chat",
},
"conversationSignature": self.conversation_signature,
"participant": {
"id": self.client_id,
},
"conversationId": self.conversation_id,
},
],
"invocationId": str(self.invocation_id),
"target": "chat",
"type": 4,
}
self.invocation_id += 1
class Conversation:
"""
Conversation API
"""
def __init__(self, cookiePath: str = "", cookies: dict | None = None) -> None:
self.struct: dict = {
"conversationId": None,
"clientId": None,
"conversationSignature": None,
"result": {
"value": "Success",
"message": None
},
}
self.session = httpx.Client()
self.session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
}, )
if cookies is not None:
cookie_file = cookies
else:
f = (
open(cookiePath, encoding="utf8").read()
if cookiePath else open(os.environ.get("COOKIE_FILE"), encoding="utf-8").read()
)
cookie_file = json.loads(f)
for cookie in cookie_file:
self.session.cookies.set(cookie["name"], cookie["value"])
url = "https://edgeservices.bing.com/edgesvc/turing/conversation/create"
# Send GET request
response = self.session.get(
url,
timeout=1800,
headers=HEADERS_INIT_CONVER,
)
if response.status_code != 200:
print(f"Status code: {response.status_code}")
print(response.text)
raise Exception("Authentication failed")
try:
self.struct = response.json()
if self.struct["result"]["value"] == "UnauthorizedRequest":
raise NotAllowedToAccess(self.struct["result"]["message"])
except (json.decoder.JSONDecodeError, NotAllowedToAccess) as exc:
raise Exception("Authentication failed. You have not been accepted into the beta.", ) from exc
class ChatHub:
"""
Chat API
"""
def __init__(self, conversation: Conversation) -> None:
self.wss: websockets.WebSocketClientProtocol | None = None
self.request: ChatHubRequest
self.loop: bool
self.task: asyncio.Task
self.request = ChatHubRequest(
conversation_signature=conversation.struct["conversationSignature"],
client_id=conversation.struct["clientId"],
conversation_id=conversation.struct["conversationId"],
)
async def ask_stream(
self,
prompt: str,
conversation_style: CONVERSATION_STYLE_TYPE = None,
) -> Generator[str, None, None]:
"""
Ask a question to the bot
"""
if self.wss:
if not self.wss.closed:
await self.wss.close()
# Check if websocket is closed
self.wss = await websockets.connect(
"wss://sydney.bing.com/sydney/ChatHub",
extra_headers=HEADERS,
max_size=None,
ssl=ssl_context,
open_timeout=30,
)
await self.__initial_handshake()
# Construct a ChatHub request
self.request.update(prompt=prompt, conversation_style=conversation_style)
# Send request
await self.wss.send(append_identifier(self.request.struct))
final = False
while not final:
objects = str(await self.wss.recv()).split(DELIMITER)
for obj in objects:
if obj is None or obj == "":
continue
response = json.loads(obj)
if response.get("type") == 1 and response["arguments"][0].get("messages", ):
yield False, response["arguments"][0]["messages"][0]["adaptiveCards"][0]["body"][0].get("text")
elif response.get("type") == 2:
final = True
yield True, response
async def __initial_handshake(self):
await self.wss.send(append_identifier({
"protocol": "json",
"version": 1
}))
await self.wss.recv()
async def close(self):
"""
Close the connection
"""
if self.wss and not self.wss.closed:
await self.wss.close()
class Chatbot:
"""
Combines everything to make it seamless
"""
def __init__(self, cookiePath: str = "", cookies: dict | None = None) -> None:
self.cookiePath: str = cookiePath
self.cookies: dict | None = cookies
self.chat_hub: ChatHub = ChatHub(Conversation(self.cookiePath, self.cookies))
async def ask(
self,
prompt: str,
conversation_style: CONVERSATION_STYLE_TYPE = None,
) -> dict:
"""
Ask a question to the bot
"""
async for final, response in self.chat_hub.ask_stream(
prompt=prompt,
conversation_style=conversation_style,
):
if final:
return response
self.chat_hub.wss.close()
async def ask_stream(
self,
prompt: str,
conversation_style: CONVERSATION_STYLE_TYPE = None,
) -> Generator[str, None, None]:
"""
Ask a question to the bot
"""
async for response in self.chat_hub.ask_stream(
prompt=prompt,
conversation_style=conversation_style,
):
yield response
async def close(self):
"""
Close the connection
"""
await self.chat_hub.close()
async def reset(self):
"""
Reset the conversation
"""
await self.close()
self.chat_hub = ChatHub(Conversation())

6
new-bing/Makefile Normal file
View File

@ -0,0 +1,6 @@
build:
docker build -t yy194131/new-bing:$(version) .
docker push yy194131/new-bing:$(version)
release:build
ssh roselle 'bash /home/linghaihui/bingchat/start.sh $(version)'

3
new-bing/README.md Normal file
View File

@ -0,0 +1,3 @@
[登录 bing 账号](https://login.live.com/) 使用[Cookie-Editor 扩展](https://chrome.google.com/webstore/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm)将 cookie 以 json 格式导出,保存为`cookie.json`文件,同时将`COOKIE_FILE`环境变量的值指向该文件。
`EdgeGPT.py`文件 fork 至[https://github.com/acheong08/EdgeGPT](https://github.com/acheong08/EdgeGPT),并做了些许修改,在此表示感谢!

108
new-bing/app.py Normal file
View File

@ -0,0 +1,108 @@
# coding=utf-8
import os
import re
import threading
import requests
from sanic import Sanic
from sanic.response import json
from EdgeGPT import Chatbot, ConversationStyle
APPID = os.environ.get('WXAPPID')
APPSECRET = os.environ.get('WXAPPSECRET')
WX_URL = 'https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code'
# 备用cookie
COOKIE = os.environ.get('COOKIE_FILE', '')
BAK_COOKIE = os.environ.get('COOKIE_FILE1', '')
BAK_COOKIE1 = os.environ.get('COOKIE_FILE2', '')
LOCK = threading.Lock()
def reset_cookie():
if not LOCK.acquire(blocking=False):
return
cookie = os.environ.get('COOKIE_FILE')
if cookie == COOKIE:
os.environ['COOKIE_FILE'] = BAK_COOKIE
elif cookie == BAK_COOKIE:
os.environ['COOKIE_FILE'] = BAK_COOKIE1
elif cookie == BAK_COOKIE1:
os.environ['COOKIE_FILE'] = COOKIE
LOCK.release()
app = Sanic('new-bing')
app.config.REQUEST_TIMEOUT = 900
app.config.RESPONSE_TIMEOUT = 900
bots = {}
def get_bot(sid):
if sid in bots:
return bots[sid]
bot = Chatbot()
bots[sid] = bot
return bot
async def do_chat(request):
return await get_bot(request.args.get('sid')).ask(
request.args.get('q'), conversation_style=ConversationStyle.balanced
)
@app.route('/chat')
async def chat(request):
res = await do_chat(request)
auto_reset = request.args.get('auto_reset', '')
status = res['item']['result']['value']
if status == 'Throttled':
reset_cookie()
await get_bot(request.args.get('sid')).reset()
res = await do_chat(request)
status = res['item']['result']['value']
text = ''
suggests = []
if status == 'Success':
if len(res['item']['messages']) >= 2:
text = res['item']['messages'][1]['text']
if re.match(r'.*\[\^\d+\^\].*', text):
text = res['item']['messages'][1]['adaptiveCards'][0]['body'][0]['text']
text = re.sub(r'\[\^\d+\^\]', '', text)
suggests = [x['text'] for x in res['item']['messages'][1]['suggestedResponses']]
else:
text = '抱歉,未搜索到结果,请重试。'
suggests = [request.args.get('q')]
msg = res['item']['result']['message'] if 'message' in res['item']['result'] else ''
# 自动reset
if auto_reset and ('New topic' in text or 'has expired' in msg):
await get_bot(request.args.get('sid')).reset()
return json({
'data': {
'status': status,
'text': text,
'suggests': suggests,
'message': msg,
},
'cookie': os.environ.get('COOKIE_FILE')
})
@app.route('/reset')
async def reset(request):
await get_bot(request.args.get('sid')).reset()
return json({'data': ''})
@app.route('/openid')
async def openid(request):
code = request.args.get('code')
url = WX_URL % (APPID, APPSECRET, code)
return json({'data': requests.get(url).json()})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000, workers=4)

5
new-bing/env.example Normal file
View File

@ -0,0 +1,5 @@
WXAPPID=
WXAPPSECRET=
COOKIE_FILE=/sanic/cookie.json
COOKIE_FILE1=/sanic/cookie1.json
COOKIE_FILE2=/sanic/cookie2.json

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,5 @@
sanic==22.12.0
requests==2.28.2
asyncio==3.4.3
websockets==10.4
httpx==0.23.3

3
new-bing/start.sh Normal file
View File

@ -0,0 +1,3 @@
docker pull yy194131/new-bing:$1
docker stop new-bing && docker rm new-bing
docker run --restart always --name new-bing --env-file $(pwd)/env -p 8000:8000 -d yy194131/new-bing:$1

1
wechatbot/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
storage.json

9
wechatbot/Dockerfile Normal file
View File

@ -0,0 +1,9 @@
FROM golang:1.16 as builder
ENV GOPROXY=https://goproxy.cn,direct
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o chatgpt
FROM alpine:3.17.2
WORKDIR /app
COPY --from=builder /app/chatgpt .
ENTRYPOINT ["./chatgpt", ">>", "chatgpt.log", "2>&1"]

3
wechatbot/Makefile Normal file
View File

@ -0,0 +1,3 @@
build:
docker build -t yy194131/chatgpt:$(version) .
docker push yy194131/chatgpt:$(version)

31
wechatbot/README.md Normal file
View File

@ -0,0 +1,31 @@
# wechatbot
最近chatGPT异常火爆想到将其接入到个人微信是件比较有趣的事所以有了这个项目。项目基于[openwechat](https://github.com/eatmoreapple/openwechat)开发支持chatGPT和New Bing[依赖于new-bing](../new-bing)
### 目前实现了以下功能
+ 群聊@回复
+ 私聊回复
+ 自动通过回复
# 注册openai
chatGPT注册可以参考[这里](https://juejin.cn/post/7173447848292253704)
# 安装使用
```
# 获取项目
git clone https://github.com/bujnlc8/gptbing
# 进入项目目录
cd gptbing/wechatbot
# 复制配置文件
copy config.dev.json config.json
# 启动项目
go run main.go
```
本项目fork至[https://github.com/djun/wechatbot](https://github.com/djun/wechatbot),在此致谢!
**⚠️ 有一定的几率导致被微信封号,请谨慎使用,由此导致的封号,本人概不负责**

View File

@ -0,0 +1,31 @@
package bootstrap
import (
"log"
"github.com/bujnlc8/wechatbot/handlers"
"github.com/eatmoreapple/openwechat"
)
func Run() {
//bot := openwechat.DefaultBot()
bot := openwechat.DefaultBot(openwechat.Desktop) // 桌面模式,上面登录不上的可以尝试切换这种模式
// 注册消息处理函数
bot.MessageHandler = handlers.Handler
// 注册登陆二维码回调
bot.UUIDCallback = openwechat.PrintlnQrcodeUrl
// 创建热存储容器对象
reloadStorage := openwechat.NewJsonFileHotReloadStorage("storage.json")
// 执行热登录
err := bot.HotLogin(reloadStorage)
if err != nil {
if err = bot.Login(); err != nil {
log.Printf("login error: %v \n", err)
return
}
}
// 阻塞主goroutine, 直到发生异常或者用户主动退出
bot.Block()
}

View File

@ -0,0 +1,5 @@
{
"api_key": "",
"auto_pass": true,
"bing_chat_url": ""
}

View File

@ -0,0 +1,57 @@
package config
import (
"encoding/json"
"log"
"os"
"sync"
)
// Configuration 项目配置
type Configuration struct {
// gpt apikey
ApiKey string `json:"api_key"`
// 自动通过好友
AutoPass bool `json:"auto_pass"`
// bing 聊天接口
BingChatUrl string `json:"bing_chat_url"`
}
var config *Configuration
var once sync.Once
// LoadConfig 加载配置
func LoadConfig() *Configuration {
once.Do(func() {
// 从文件中读取
config = &Configuration{}
f, err := os.Open("config.json")
if err != nil {
log.Fatalf("open config err: %v", err)
return
}
defer f.Close()
encoder := json.NewDecoder(f)
err = encoder.Decode(config)
if err != nil {
log.Fatalf("decode config err: %v", err)
return
}
// 如果环境变量有配置,读取环境变量
ApiKey := os.Getenv("ApiKey")
AutoPass := os.Getenv("AutoPass")
BingChatUrl := os.Getenv("BingChatUrl")
if ApiKey != "" {
config.ApiKey = ApiKey
}
if AutoPass == "true" {
config.AutoPass = true
}
if BingChatUrl != "" {
config.BingChatUrl = BingChatUrl
}
})
return config
}

5
wechatbot/go.mod Normal file
View File

@ -0,0 +1,5 @@
module github.com/bujnlc8/wechatbot
go 1.16
require github.com/eatmoreapple/openwechat v1.4.2-0.20230321053318-1c102bdea8d7

2
wechatbot/go.sum Normal file
View File

@ -0,0 +1,2 @@
github.com/eatmoreapple/openwechat v1.4.2-0.20230321053318-1c102bdea8d7 h1:5vryo6dsmZZn1pYo8TrcqEUJq075QMpdDwLZz+oMfLs=
github.com/eatmoreapple/openwechat v1.4.2-0.20230321053318-1c102bdea8d7/go.mod h1:ZxMcq7IpVWVU9JG7ERjExnm5M8/AQ6yZTtX30K3rwRQ=

85
wechatbot/gpt/bing.go Normal file
View File

@ -0,0 +1,85 @@
package gpt
import (
"encoding/json"
"io/ioutil"
"log"
"net/http"
"net/url"
"strings"
"github.com/bujnlc8/wechatbot/config"
)
type BingQuery struct {
Q string `json:"q"`
SID string `json:"sid"`
}
type BingResponse struct {
Data BingResponseData `json:"data"`
Cookie string `json:"cookie"`
}
type BingResponseData struct {
Suggests []string `json:"suggests"`
Status string `json:"status"`
Text string `json:"text"`
Message string `json:"message"`
}
// const BingChatUrl = "http://127.0.0.1:8000/chat"
const Referer = "https://servicewechat.com/wxee7496be5b68b740"
func BingSearch(msg string, nickName string) (string, error) {
params := url.Values{}
params.Add("q", msg)
params.Add("sid", nickName)
params.Add("auto_reset", "1")
log.Printf("request bing query string : %v", params)
BingChatUrl := config.LoadConfig().BingChatUrl
req, err := http.NewRequest("GET", BingChatUrl+"?"+params.Encode(), nil)
if err != nil {
return "", err
}
req.Header.Set("Referer", Referer)
client := &http.Client{}
response, err := client.Do(req)
if err != nil {
return "非常抱歉😭,网络异常,请稍后重试", err
}
if response.StatusCode != 200 {
return "非常抱歉😭,网络异常,请稍后重试 [" + string(rune(response.StatusCode)) + "]", nil
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return "响应异常,请稍后再试", err
}
bingResponse := &BingResponse{}
log.Println(string(body))
err = json.Unmarshal(body, bingResponse)
if err != nil {
return "", err
}
if bingResponse.Data.Status == "Success" {
if strings.Contains(bingResponse.Data.Text, "New topic") {
return bingResponse.Data.Text + "\n请重新开始对话", nil
}
return bingResponse.Data.Text, nil
} else {
if bingResponse.Data.Status == "Throttled" {
return "这真是愉快,但你已达到每日限制。是否明天再聊?", nil
} else {
if strings.Contains(bingResponse.Data.Message, "has expired") {
return "本轮对话已过期,请重新开始。", nil
} else {
return "抱歉😭,发生错误:" + bingResponse.Data.Message + ",请重试", nil
}
}
}
}

View File

@ -0,0 +1,15 @@
package gpt
import (
"fmt"
"testing"
)
func TestBing(t *testing.T) {
reply, err := BingSearch("今天北京的天气怎么样", "nickname")
if err != nil{
t.Error(err)
}
fmt.Printf("%+v\n", reply)
}

92
wechatbot/gpt/gpt.go Normal file
View File

@ -0,0 +1,92 @@
package gpt
import (
"bytes"
"encoding/json"
"io/ioutil"
"log"
"net/http"
"github.com/bujnlc8/wechatbot/config"
)
const BASEURL = "https://api.openai.com/v1/chat/"
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
}
// ChatGPTResponseBody 请求体
type ChatGPTResponseBody struct {
ID string `json:"id"`
Object string `json:"object"`
Created int `json:"created"`
Model string `json:"model"`
Choices []ChoiceItem `json:"choices"`
Usage map[string]interface{} `json:"usage"`
}
type ChoiceItem struct {
Message Message `json:"message"`
FinishReason string `json:"finish_reason"`
}
// ChatGPTRequestBody 响应体
type ChatGPTRequestBody struct {
Model string `json:"model"`
MaxTokens int `json:"max_tokens"`
Temperature float32 `json:"temperature"`
Messages []Message `json:"messages"`
}
func Completions(msg string) (string, error) {
message := Message{Role: "user", Content: msg}
requestBody := ChatGPTRequestBody{
Model: "gpt-3.5-turbo",
MaxTokens: 2048,
Temperature: 0.2,
Messages: []Message{message},
}
requestData, err := json.Marshal(requestBody)
if err != nil {
return "", err
}
log.Printf("request gpt json string : %v", string(requestData))
req, err := http.NewRequest("POST", BASEURL+"completions", bytes.NewBuffer(requestData))
if err != nil {
return "", err
}
apiKey := config.LoadConfig().ApiKey
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+apiKey)
client := &http.Client{}
response, err := client.Do(req)
if err != nil {
return "", err
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return "", err
}
gptResponseBody := &ChatGPTResponseBody{}
log.Println(string(body))
err = json.Unmarshal(body, gptResponseBody)
if err != nil {
return "", err
}
var reply string
if len(gptResponseBody.Choices) > 0 {
for _, v := range gptResponseBody.Choices {
reply = v.Message.Content
break
}
}
log.Printf("gpt response text: %s \n", reply)
return reply, nil
}

View File

@ -0,0 +1,78 @@
package handlers
import (
"log"
"strings"
"github.com/bujnlc8/wechatbot/gpt"
"github.com/eatmoreapple/openwechat"
)
var _ MessageHandlerInterface = (*GroupMessageHandler)(nil)
// GroupMessageHandler 群消息处理
type GroupMessageHandler struct {
}
// handle 处理消息
func (g *GroupMessageHandler) handle(msg *openwechat.Message) error {
if msg.IsText() {
return g.ReplyText(msg)
}
msg.ReplyText("目前我只支持文字哦~")
return nil
}
// NewGroupMessageHandler 创建群消息处理器
func NewGroupMessageHandler() MessageHandlerInterface {
return &GroupMessageHandler{}
}
// ReplyText 发送文本消息到群
func (g *GroupMessageHandler) ReplyText(msg *openwechat.Message) error {
sender, err := msg.Sender()
group := openwechat.Group{User: sender}
log.Printf("Received Group %v Text Msg : %v", group.NickName, msg.Content)
// @GPTBot 或者 @bing的消息才处理
if !(strings.Contains(msg.Content, "@GPTBot") || strings.Contains(msg.Content, "@bing")) {
return nil
}
requestText := strings.TrimSpace(strings.ReplaceAll(msg.Content, "@GPTBot", ""))
var reply = ""
if strings.Contains(msg.Content, "@bing") {
requestText = strings.TrimSpace(strings.ReplaceAll(msg.Content, "@bing", ""))
reply, err = gpt.BingSearch(requestText, sender.NickName)
} else {
reply, err = gpt.Completions(requestText)
}
if err != nil {
log.Printf("gpt request error: %v \n", err)
msg.ReplyText("机器人神了,我一会发现了就去修。")
return err
}
if reply == "" {
msg.ReplyText("机器人响应为空")
return nil
}
// 获取@我的用户
groupSender, err := msg.SenderInGroup()
if err != nil {
log.Printf("get sender in group error :%v \n", err)
return err
}
// 回复@我的用户
reply = strings.TrimSpace(reply)
reply = strings.Trim(reply, "\n")
atText := "@" + groupSender.NickName
replyText := atText + " " + reply
_, err = msg.ReplyText(replyText)
if err != nil {
log.Printf("response group error: %v \n", err)
}
return err
}

View File

@ -0,0 +1,54 @@
package handlers
import (
"log"
"github.com/bujnlc8/wechatbot/config"
"github.com/eatmoreapple/openwechat"
)
// MessageHandlerInterface 消息处理接口
type MessageHandlerInterface interface {
handle(*openwechat.Message) error
ReplyText(*openwechat.Message) error
}
type HandlerType string
const (
GroupHandler = "group"
UserHandler = "user"
)
// handlers 所有消息类型类型的处理器
var handlers map[HandlerType]MessageHandlerInterface
func init() {
handlers = make(map[HandlerType]MessageHandlerInterface)
handlers[GroupHandler] = NewGroupMessageHandler()
handlers[UserHandler] = NewUserMessageHandler()
}
// Handler 全局处理入口
func Handler(msg *openwechat.Message) {
log.Printf("hadler Received msg : %v", msg.Content)
// 处理群消息
if msg.IsSendByGroup() {
handlers[GroupHandler].handle(msg)
return
}
// 好友申请
if msg.IsFriendAdd() {
if config.LoadConfig().AutoPass {
_, err := msg.Agree("你好我是基于chatGPT引擎开发的微信机器人你可以向我提问任何问题。")
if err != nil {
log.Fatalf("add friend agree error : %v", err)
return
}
}
}
// 私聊
handlers[UserHandler].handle(msg)
}

View File

@ -0,0 +1,65 @@
package handlers
import (
"log"
"strings"
"github.com/bujnlc8/wechatbot/gpt"
"github.com/eatmoreapple/openwechat"
)
var _ MessageHandlerInterface = (*UserMessageHandler)(nil)
// UserMessageHandler 私聊消息处理
type UserMessageHandler struct {
}
// handle 处理消息
func (g *UserMessageHandler) handle(msg *openwechat.Message) error {
if msg.IsText() {
return g.ReplyText(msg)
}
msg.ReplyText("目前我只支持文字哦~")
return nil
}
// NewUserMessageHandler 创建私聊处理器
func NewUserMessageHandler() MessageHandlerInterface {
return &UserMessageHandler{}
}
// ReplyText 发送文本消息到群
func (g *UserMessageHandler) ReplyText(msg *openwechat.Message) error {
// 接收私聊消息
sender, err := msg.Sender()
log.Printf("Received User %v Text Msg : %v", sender.NickName, msg.Content)
requestText := strings.TrimSpace(msg.Content)
requestText = strings.Trim(msg.Content, "\n")
var reply = ""
if strings.Contains(msg.Content, "@bing") {
requestText = strings.TrimSpace(strings.ReplaceAll(msg.Content, "@bing", ""))
reply, err = gpt.BingSearch(requestText, sender.NickName)
} else {
reply, err = gpt.Completions(requestText)
}
if err != nil {
log.Printf("gpt request error: %v \n", err)
msg.ReplyText("机器人神了,我一会发现了就去修。")
return err
}
if reply == "" {
msg.ReplyText("机器人响应为空")
return nil
}
// 回复用户
reply = strings.TrimSpace(reply)
reply = strings.Trim(reply, "\n")
_, err = msg.ReplyText(reply)
if err != nil {
log.Printf("response user error: %v \n", err)
}
return err
}

9
wechatbot/main.go Normal file
View File

@ -0,0 +1,9 @@
package main
import (
"github.com/bujnlc8/wechatbot/bootstrap"
)
func main() {
bootstrap.Run()
}