mirror of
https://github.com/wechaty/getting-started.git
synced 2026-10-17 05:20:49 +08:00
New Demo Example: Wechat Chatbot Powered by OpenAI ChatGPT (#284)
* feat: added wechaty chatgpt integration * fix: correct formatting --------- Co-authored-by: Daniel Zheng <daniel@cybever.com>
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
# Wechaty ChatGPT
|
||||
|
||||
This is a wechaty implementation of ChatBot powered by OpenAI ChatGPT
|
||||
|
||||
## Setup
|
||||
|
||||
Modify `dotenv` file to add your openai_api_key as well as the service_token
|
||||
```
|
||||
OPENAI_API_KEY=""
|
||||
WECHATY_PUPPET_SERVICE_TOKEN=""
|
||||
```
|
||||
|
||||
```shell
|
||||
npm install
|
||||
mv dotenv .env
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```shell
|
||||
npm run start
|
||||
```
|
||||
@@ -0,0 +1,49 @@
|
||||
// Importing the Wechaty npm package
|
||||
import { Wechaty,WechatyBuilder, Contact, Message, ScanStatus, log } from "wechaty";
|
||||
import { Configuration, OpenAIApi, ChatCompletionRequestMessage } from "openai";
|
||||
import { PuppetPadlocal } from "wechaty-puppet-padlocal";
|
||||
import { onScan, onLogin, onLogout, onMessage, onFriendship } from "./utils";
|
||||
import dotenv from "dotenv";
|
||||
|
||||
dotenv.config();
|
||||
console.log(process.env.OPENAI_API_KEY)
|
||||
|
||||
|
||||
export function createBot(): Wechaty {
|
||||
const token: string = process.env.WECHATY_PUPPET_SERVICE_TOKEN!
|
||||
const puppet = new PuppetPadlocal({
|
||||
token,
|
||||
});
|
||||
|
||||
return WechatyBuilder.build({
|
||||
name: "chatgpt-bot",
|
||||
puppet,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// config openAI
|
||||
const configuration = new Configuration({
|
||||
apiKey: process.env.OPENAI_API_KEY,
|
||||
});
|
||||
export const openai = new OpenAIApi(configuration);
|
||||
|
||||
// Initializing the bot
|
||||
export const bot = createBot();
|
||||
|
||||
// Keep the conversation state
|
||||
export const initState: Array<ChatCompletionRequestMessage> = new Array({ "role": "system", "content": "You are a helpful assistant." })
|
||||
|
||||
bot.on('scan', onScan)
|
||||
bot.on('login', onLogin)
|
||||
bot.on('logout', onLogout)
|
||||
bot.on('message', onMessage)
|
||||
bot.on('friendship', onFriendship)
|
||||
|
||||
bot.start()
|
||||
.then(() => log.info('StarterBot', 'Starter Bot Started.'))
|
||||
.catch(e => log.error('StarterBot', e))
|
||||
|
||||
bot.ready()
|
||||
.then(() => log.info('StarterBot', 'Starter Bot Ready.'))
|
||||
.catch(e => log.error('StarterBot', e))
|
||||
@@ -0,0 +1,2 @@
|
||||
OPENAI_API_KEY=""
|
||||
WECHATY_PUPPET_SERVICE_TOKEN=""
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "wechaty-chatgpt",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "npx ts-node bot.ts",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"dotenv": "^16.0.3",
|
||||
"file-box": "^1.5.5",
|
||||
"markdown-it": "^13.0.1",
|
||||
"node-html-to-image": "^3.3.0",
|
||||
"openai": "^3.2.1",
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"wechaty": "^1.20.2",
|
||||
"wechaty-puppet-padlocal": "^1.20.1",
|
||||
"wechaty-puppet-service": "^1.19.9",
|
||||
"wechaty-puppet-wechat": "^1.18.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/markdown-it": "^12.2.3",
|
||||
"@types/qrcode-terminal": "^0.12.0"
|
||||
}
|
||||
}
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export WECHATY_LOG=verbose
|
||||
export WECHATY_PUPPET=wechaty-puppet-service
|
||||
|
||||
npm run start
|
||||
@@ -0,0 +1,157 @@
|
||||
import { Contact, Message, ScanStatus, log, Friendship } from "wechaty";
|
||||
import { bot, openai, initState } from "./bot";
|
||||
import markdownIt from 'markdown-it';
|
||||
import { ChatCompletionRequestMessage } from "openai";
|
||||
import { FileBox } from "file-box";
|
||||
import qrTerm from "qrcode-terminal";
|
||||
import nodeHtmlToImage from 'node-html-to-image';
|
||||
|
||||
const MEMORY_LIMIT = 50; // max memory
|
||||
let conversation: Array<ChatCompletionRequestMessage> = new Array();
|
||||
conversation.forEach(val => initState.push(Object.assign({}, val)));
|
||||
|
||||
function convertMarkdownToHtml(markdown: string): string {
|
||||
const md = new markdownIt();
|
||||
return md.render(markdown);
|
||||
}
|
||||
|
||||
export function onScan (qrcode: string, status: ScanStatus) {
|
||||
if (status === ScanStatus.Waiting || status === ScanStatus.Timeout) {
|
||||
qrTerm.generate(qrcode, { small: true }) // show qrcode on console
|
||||
|
||||
const qrcodeImageUrl = [
|
||||
'https://wechaty.js.org/qrcode/',
|
||||
encodeURIComponent(qrcode),
|
||||
].join('')
|
||||
|
||||
log.info('StarterBot', 'onScan: %s(%s) - %s', ScanStatus[status], status, qrcodeImageUrl)
|
||||
} else {
|
||||
log.info('StarterBot', 'onScan: %s(%s)', ScanStatus[status], status)
|
||||
}
|
||||
}
|
||||
|
||||
export function onLogin(user: Contact) {
|
||||
log.info('StarterBot', '%s login', user)
|
||||
}
|
||||
|
||||
export function onLogout(user: Contact) {
|
||||
log.info('StarterBot', '%s logout', user)
|
||||
}
|
||||
|
||||
export async function onMessage(msg: Message) {
|
||||
log.info('StarterBot', msg.toString())
|
||||
|
||||
const contact = msg.talker();
|
||||
const content = msg.text();
|
||||
const isText = msg.type() === bot.Message.Type.Text;
|
||||
if (msg.self() || !isText) { // msg.self() check if the message is sent from the bot itself
|
||||
return;
|
||||
}
|
||||
if (content === 'ding') {
|
||||
await contact.say('dong');
|
||||
}
|
||||
// return image
|
||||
if (content.startsWith("/img ")) {
|
||||
if (conversation.length === MEMORY_LIMIT) {
|
||||
// reset to initial state when reach the memory limit
|
||||
log.info("Resetting memory");
|
||||
conversation = new Array();
|
||||
conversation.forEach(val => initState.push(Object.assign({}, val)));
|
||||
}
|
||||
conversation.push({ "role": "user", "content": content.replace("/i", "") })
|
||||
const response = await openai.createChatCompletion({
|
||||
model: "gpt-3.5-turbo",
|
||||
messages: conversation,
|
||||
});
|
||||
|
||||
try {
|
||||
const replyContent = response.data.choices[0].message!.content!
|
||||
const html = convertMarkdownToHtml(replyContent);
|
||||
await nodeHtmlToImage({
|
||||
output: './output.png',
|
||||
html: html
|
||||
})
|
||||
|
||||
log.info('The image was created successfully!')
|
||||
const fileBox = FileBox.fromFile("./output.png");
|
||||
await contact.say(fileBox)
|
||||
|
||||
// record reply
|
||||
const reply: ChatCompletionRequestMessage = { "role": "assistant", "content": replyContent };
|
||||
conversation.push(reply);
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
} else {
|
||||
// return text if no slash command is specified
|
||||
if (conversation.length === MEMORY_LIMIT) {
|
||||
// reset to initial state when reach the memory limit
|
||||
log.info("Resetting memory");
|
||||
conversation = new Array();
|
||||
conversation.forEach(val => initState.push(Object.assign({}, val)));
|
||||
}
|
||||
conversation.push({ "role": "user", "content": content.replace("/t", "") })
|
||||
const response = await openai.createChatCompletion({
|
||||
model: "gpt-3.5-turbo",
|
||||
messages: conversation,
|
||||
});
|
||||
|
||||
try {
|
||||
const replyContent = response.data.choices[0].message!.content!
|
||||
await contact.say(replyContent);
|
||||
|
||||
// record reply
|
||||
const reply: ChatCompletionRequestMessage = { "role": "assistant", "content": replyContent };
|
||||
conversation.push(reply);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function onFriendship(friendship: Friendship) {
|
||||
let logMsg;
|
||||
|
||||
try {
|
||||
logMsg = "received `friend` event from " + friendship.contact().name();
|
||||
log.info(logMsg);
|
||||
|
||||
switch (friendship.type()) {
|
||||
/**
|
||||
*
|
||||
* 1. New Friend Request
|
||||
*
|
||||
* when request is set, we can get verify message from `request.hello`,
|
||||
* and accept this request by `request.accept()`
|
||||
*/
|
||||
|
||||
case bot.Friendship.Type.Receive:
|
||||
logMsg = 'accepted automatically';
|
||||
log.info("before accept");
|
||||
await friendship.accept();
|
||||
|
||||
// if want to send msg , you need to delay sometimes
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
await friendship.contact().say(`Hi ${friendship.contact().name()} from FreeChatGPT, I am your person asistant!\n你好 ${friendship.contact().name()} 我是你的私人助理FreeChatGPT!`);
|
||||
console.log("after accept");
|
||||
break;
|
||||
|
||||
/**
|
||||
*
|
||||
* 2. Friend Ship Confirmed
|
||||
*
|
||||
*/
|
||||
case bot.Friendship.Type.Confirm:
|
||||
logMsg = "friendship confirmed with " + friendship.contact().name();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
logMsg = "Friendship try catch failed";
|
||||
}
|
||||
|
||||
log.info(logMsg);
|
||||
}
|
||||
@@ -8,7 +8,7 @@ test('wechaty-puppet-wechat', async t => {
|
||||
puppet: 'wechaty-puppet-wechat',
|
||||
puppetOptions: {
|
||||
launchOptions: {
|
||||
ignoreDefaultArgs: ['--disable-extensions'],
|
||||
ignoreDefaultArgs: [ '--disable-extensions' ],
|
||||
},
|
||||
},
|
||||
} as const
|
||||
|
||||
Reference in New Issue
Block a user