國內服務器 3 分鐘將 ChatGPT 接入微信公衆號(超詳細)

🔗原文鏈接:https://forum.laf.run/d/364

最近很火ChatGPT可以說已經滿大街可見了,到處都有各種各樣的體驗地址,有收費的也有免費的,總之是五花八門、花裏胡哨。

所以呢,最近我就在研究怎麼才能方便快捷的體驗到ChatGPT的強大功能,其中一個就是:把ChatGPT接入公衆號。畢竟公衆號是一種非常流行的社交媒體平臺,可以用來提供服務、推廣產品等。經過我的一番折騰,最後終於成功通過 Laf 實現了這個需求。

本文將詳細介紹如何使用 Laf 雲平臺將 ChatGPT 接入微信公衆號,實現智能問答、聊天機器人等功能。

1. 準備工作

首先需要註冊一個 Laf 平臺賬號:https://laf.run

註冊登錄之後,點擊新建,建立一個應用:

點擊開發,進入應用開發界面:

然後輸入 chatgpt 並回車進行搜索,選擇第一個搜索結果,保存並重啓:

重啓之後,自定義依賴項中便出現了 chatgpt。

新建雲函數

然後我們點擊函數,函數列表右側的加號,新增一個可以接入微信公衆號的 ChatGPT 雲函數:

雲函數完整代碼如下:

// 引入crypto和cloud模塊
import * as crypto from 'crypto';
import cloud from '@lafjs/cloud';

const WAIT_MESSAGE = `處理中 ... \n\n請稍等3秒後發送【1】查看回復`
const NO_MESSAGE = `暫無內容,請稍後回覆【1】再試`
const CLEAR_MESSAGE = `✅ 記憶已清除`
const HELP_MESSAGE = `ChatGPT 指令使用指南
   |    關鍵字  |   功能         |
   |      1    | 上一次問題的回覆 |
   |   /clear  |    清除上下文   |
   |   /help   |   獲取更多幫助  |
  `

// 不支持的消息類型
const UNSUPPORTED_MESSAGE_TYPES = {
  image: '暫不支持圖片消息',
  voice: '暫不支持語音消息',
  video: '暫不支持視頻消息',
  music: '暫不支持音樂消息',
  news: '暫不支持圖文消息',
}

// 定義休眠函數
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

// 創建數據庫連接並獲取Message集合
const db = cloud.database();
const Message = db.collection('messages')

// 處理接收到的微信公衆號消息
export async function main(event) {
  const { signature, timestamp, nonce, echostr } = event.query;
  const token = '123456';

  // 驗證消息是否合法,若不合法則返回錯誤信息
  if (!verifySignature(signature, timestamp, nonce, token)) {
    return 'Invalid signature';
  }

  // 如果是首次驗證,則返回 echostr 給微信服務器
  if (echostr) {
    return echostr;
  }

  // 處理接收到的消息
  const payload = event.body.xml;
  // console.log("payload",payload)
  // 文本消息
  if (payload.msgtype[0] === 'text') {
    const newMessage = {
      msgid: payload.msgid[0],
      question: payload.content[0].trim(),
      username: payload.fromusername[0],
      sessionId: payload.fromusername[0],
      createdAt: Date.now()
    }

    // 修復請求響應超時問題:如果 5 秒內 AI 沒有回覆,則返回等待消息
    const responseText = await Promise.race([
      replyText(newMessage),
      sleep(4000.0).then(() => WAIT_MESSAGE),
    ]);
    return toXML(payload, responseText);
  }

  // 公衆號事件
  if (payload.msgtype[0] === 'event') {
    // 公衆號訂閱
    if (payload.event[0] === 'subscribe') {
      return toXML(payload, HELP_MESSAGE);
    }
  }

  // 暫不支持的消息類型
  if (payload.MsgType in UNSUPPORTED_MESSAGE_TYPES) {
    const responseText = UNSUPPORTED_MESSAGE_TYPES[payload.MsgType];
    return toXML(payload, responseText);
  }

  return 'success'
}


// 處理文本回復消息
async function replyText(message) {
  const { question, sessionId } = message;
  console.log("replyText 執行了")
  // 檢查是否是重試操作,如果是重試操作,返回上一次的回覆
  if (question === '1') {
    const lastMessage = await Message.where({
      sessionId
    }).orderBy("createdAt", "desc").get();
    if (lastMessage.data[0]) {
      return `${lastMessage.data[0].question}\n------------\n${lastMessage.data[0].answer}`;
    }

    return NO_MESSAGE;
  }

  // 獲取上下文 id
  const res = await Message.where({
    sessionId
  }).orderBy("createdAt", "desc").getOne();

  const parentId = res?.data?.parentMessageId
  const conId = res?.data?.conversationId


  // 發送指令
  if (question.startsWith('/')) {
    return await processCommandText(message);
  }

  // 獲取 OpenAI 回覆內容
  const { error, answer, parentMessageId, conversationId } = await getOpenAIReply(question, parentId, conId);
  if (error) {
    console.error(`sessionId: ${sessionId}; question: ${question}; error: ${error}`);
    return error;
  }

  // 將消息保存到數據庫中
  const token = question.length + answer.length;
  const result = await Message.add({ token, answer, parentMessageId, conversationId, ...message });
  console.debug(`[save message] result: ${result}`);

  return answer;
}

// 獲取 OpenAI API 的回覆
async function getOpenAIReply(question, parentId, conId) {
  console.log("getOpenAIReply 執行了")
  // 引入 ChatGPTUnofficialProxyAPI 模塊
  const { ChatGPTUnofficialProxyAPI } = await import('chatgpt')
  // 創建 ChatGPTUnofficialProxyAPI 實例
  const api = new ChatGPTUnofficialProxyAPI({
    accessToken: cloud.env.ACCESSTOKEN,
    apiReverseProxyUrl: "https://bypass.churchless.tech/api/conversation"
  })


  try {
    // 如果有上下文 id,就帶上
    let res;

    if (parentId && conId) {
      res = await api.sendMessage(question, { conversationId: conId, parentMessageId: parentId })
    } else {
      res = await api.sendMessage(question)
    }
    // 返回 OpenAI 回覆的內容及上下文 id
    return { answer: res.text.replace("\n\n", ""), parentMessageId: res.parentMessageId, conversationId: res.conversationId }

  } catch (e) {
    console.log(e)
    return {
      error: "問題太難了 出錯了. (uДu〃).",
    }
  }

}

// 校驗微信服務器發送的消息是否合法
function verifySignature(signature, timestamp, nonce, token) {
  const arr = [token, timestamp, nonce].sort();
  const str = arr.join('');
  const sha1 = crypto.createHash('sha1');
  sha1.update(str);
  return sha1.digest('hex') === signature;
}

// 返回組裝 xml
function toXML(payload, content) {
  const timestamp = Date.now();
  const { tousername: fromUserName, fromusername: toUserName } = payload;
  return `
  <xml>
    <ToUserName><![CDATA[${toUserName}]]></ToUserName>
    <FromUserName><![CDATA[${fromUserName}]]></FromUserName>
    <CreateTime>${timestamp}</CreateTime>
    <MsgType><![CDATA[text]]></MsgType>
    <Content><![CDATA[${content}]]></Content>
  </xml>
  `
}

async function processCommandText({ sessionId, question }) {
  // 清理歷史會話
  if (question === '/clear') {
    const res = await Message.where({ sessionId }).remove({ multi: true })
    return CLEAR_MESSAGE;
  } else {
    return HELP_MESSAGE;
  }
}

由於 OpenAI 的 API Key 需要充值才能用,所以我們選擇劍走偏鋒,直接使用 ChatGPT 網頁版。但是國內環境無法訪問 ChatGPT,所以我們需要一個 Proxy。不用擔心,國外已經有熱心小哥給我們提供了公共的 Proxy,我們只需要直接調用就好啦!

詳情可參考 ChatGPTUnofficialProxyAPI 的使用文檔

只要你有 ChatGPT 賬號,都可以使用這種方法,最🐮🍺的是,你可以直接在 Laf 的國內環境 https://laf.run 中使用!!!

核心函數:

  • getOpenAIReply函數通過引入ChatGPTUnofficialProxyAPI模塊,創建實例並調用其方法,獲取 ChatGPT 的回覆內容。
  • verifySignature函數用於校驗微信服務器發送的消息是否合法。
  • toXML函數將回復內容組裝成XML格式。
  • processCommandText函數用於處理指令,目前支持清除歷史會話獲取幫助信息兩個指令。

注意:

  • token要與微信公衆號中設置一致。
  • ACCESSTOKEN 的獲取方式:你需要先在瀏覽器中登錄 ChatGPT 網頁版,然後在瀏覽器中訪問這個 URL:https://chat.openai.com/api/auth/session,瀏覽器會返回一個 JSON,裏面包含了 accessToken 字段,將這個字段的值複製即可。

雲函數寫完之後就點擊發布,左側的接口地址要保存一下,稍後將在微信公衆號中使用此地址。

配置微信公衆號

這一步我們需要在微信公衆號平臺上配置開發者信息,並將服務器地址設置爲部署好的雲函數服務地址。步驟如下:

首先你需要有一個公衆號,然後登錄微信公衆平臺,點開左側的「設置與開發」,點擊「基本設置」,然後點擊「服務器配置」,服務器配置那裏點擊修改配置:

將雲函數服務地址複製到「服務器 URL」中,下邊的 Token 與雲函數代碼中的 token 保持一致,下邊的 EncodingAESKey 點擊右側隨機生成就行,然後點擊提交:

返回 token 校驗成功後,點擊「啓用」即可。

現在你已經完成了所有必要的設置和配置,下面就可以直接進入微信公衆號「Laf 開發者」後臺與機器人進行交互啦!

ChatGPT 機器人可以回答用戶提出的問題,並且可以根據用戶提供的上下文進行回覆。以下是一些指令和關鍵字,可以幫助您更好地使用 ChatGPT 機器人:

  • 【1】:獲取上一次問題的回覆。
  • /clear:清除上下文。
  • /help:獲取更多幫助。 除了以上指令和關鍵字外,你還可以根據自己的需求進行定製化開發,以滿足用戶的需求。

總結

通過以上步驟,您已經成功地將 ChatGPT 接入微信公衆號,並使用 Laf 平臺來部署 ChatGPT 雲函數。這樣,您就可以在公衆號中爲您的用戶提供智能問答、聊天機器人等服務了,微信公衆號自帶的自動回覆功能可以拋棄了。

當然,直接接入 ChatGPT 網頁版肯定沒有接入 API 穩定,如果你想通過 API 的方式接入,可以參考原作者的官方倉庫:

最後,歡迎關注我們的公衆號直接與 ChatGPT 對話吧!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章