initial commit
This commit is contained in:
commit
f173af8104
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
venv/
|
||||
__pycache__/
|
||||
cookie*.json
|
||||
env
|
||||
config.json
|
||||
1
README.md
Normal file
1
README.md
Normal file
@ -0,0 +1 @@
|
||||
本项目实现了[New Bing 聊天的接口](./new-bing),并做了一个简单的[小程序](./bingchat),同时将`chatgpt 3.5`和`New Bing`集成到[微信](./wechatbot)。
|
||||
3
bingchat/README.md
Normal file
3
bingchat/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# New Bing Bot
|
||||
|
||||
<image style="width: 320px" src="./snapshoot.png"/>
|
||||
35
bingchat/app.js
Normal file
35
bingchat/app.js
Normal 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
21
bingchat/app.json
Normal 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
4
bingchat/app.wxss
Normal file
@ -0,0 +1,4 @@
|
||||
page {
|
||||
background-color: #fff;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
94
bingchat/components/chatbox/index.js
Normal file
94
bingchat/components/chatbox/index.js
Normal 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,
|
||||
},
|
||||
{}
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
4
bingchat/components/chatbox/index.json
Normal file
4
bingchat/components/chatbox/index.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
22
bingchat/components/chatbox/index.wxml
Normal file
22
bingchat/components/chatbox/index.wxml
Normal 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>
|
||||
77
bingchat/components/chatbox/index.wxss
Normal file
77
bingchat/components/chatbox/index.wxss
Normal 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
1
bingchat/config.js
Normal file
@ -0,0 +1 @@
|
||||
export default SERVER_HOST = 'https://example.com';
|
||||
BIN
bingchat/image/bing-avatar.png
Normal file
BIN
bingchat/image/bing-avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
BIN
bingchat/image/person.jpeg
Normal file
BIN
bingchat/image/person.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.0 KiB |
222
bingchat/pages/index/index.js
Normal file
222
bingchat/pages/index/index.js
Normal 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);
|
||||
},
|
||||
});
|
||||
4
bingchat/pages/index/index.json
Normal file
4
bingchat/pages/index/index.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
}
|
||||
}
|
||||
4
bingchat/pages/index/index.wxml
Normal file
4
bingchat/pages/index/index.wxml
Normal 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>
|
||||
0
bingchat/pages/index/index.wxss
Normal file
0
bingchat/pages/index/index.wxss
Normal file
27
bingchat/project.config.json
Normal file
27
bingchat/project.config.json
Normal 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
|
||||
}
|
||||
}
|
||||
9
bingchat/project.private.config.json
Normal file
9
bingchat/project.private.config.json
Normal 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
7
bingchat/sitemap.json
Normal 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
BIN
bingchat/snapshoot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 274 KiB |
13
new-bing/Dockerfile
Normal file
13
new-bing/Dockerfile
Normal 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
336
new-bing/EdgeGPT.py
Normal 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
6
new-bing/Makefile
Normal 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
3
new-bing/README.md
Normal 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
108
new-bing/app.py
Normal 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
5
new-bing/env.example
Normal file
@ -0,0 +1,5 @@
|
||||
WXAPPID=
|
||||
WXAPPSECRET=
|
||||
COOKIE_FILE=/sanic/cookie.json
|
||||
COOKIE_FILE1=/sanic/cookie1.json
|
||||
COOKIE_FILE2=/sanic/cookie2.json
|
||||
1
new-bing/pyrightconfig.json
Normal file
1
new-bing/pyrightconfig.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
||||
5
new-bing/requirements.txt
Normal file
5
new-bing/requirements.txt
Normal 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
3
new-bing/start.sh
Normal 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
1
wechatbot/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
storage.json
|
||||
9
wechatbot/Dockerfile
Normal file
9
wechatbot/Dockerfile
Normal 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
3
wechatbot/Makefile
Normal file
@ -0,0 +1,3 @@
|
||||
build:
|
||||
docker build -t yy194131/chatgpt:$(version) .
|
||||
docker push yy194131/chatgpt:$(version)
|
||||
31
wechatbot/README.md
Normal file
31
wechatbot/README.md
Normal 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),在此致谢!
|
||||
|
||||
**⚠️ 有一定的几率导致被微信封号,请谨慎使用,由此导致的封号,本人概不负责**
|
||||
31
wechatbot/bootstrap/bootstrap.go
Normal file
31
wechatbot/bootstrap/bootstrap.go
Normal 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()
|
||||
}
|
||||
5
wechatbot/config.json.example
Normal file
5
wechatbot/config.json.example
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"api_key": "",
|
||||
"auto_pass": true,
|
||||
"bing_chat_url": ""
|
||||
}
|
||||
57
wechatbot/config/config.go
Normal file
57
wechatbot/config/config.go
Normal 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
5
wechatbot/go.mod
Normal 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
2
wechatbot/go.sum
Normal 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
85
wechatbot/gpt/bing.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
wechatbot/gpt/bing_test.go
Normal file
15
wechatbot/gpt/bing_test.go
Normal 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
92
wechatbot/gpt/gpt.go
Normal 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
|
||||
}
|
||||
78
wechatbot/handlers/group_msg_handler.go
Normal file
78
wechatbot/handlers/group_msg_handler.go
Normal 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
|
||||
}
|
||||
54
wechatbot/handlers/handler.go
Normal file
54
wechatbot/handlers/handler.go
Normal 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)
|
||||
}
|
||||
65
wechatbot/handlers/user_msg_handler.go
Normal file
65
wechatbot/handlers/user_msg_handler.go
Normal 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
9
wechatbot/main.go
Normal file
@ -0,0 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/bujnlc8/wechatbot/bootstrap"
|
||||
)
|
||||
|
||||
func main() {
|
||||
bootstrap.Run()
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user