这个版本的代码开始我是作为玩具写的,已经好多次想要重构迁移到一个新的项目里了,但是每次做到一半都发现还是在老项目里改更加方便……于是它就一直被我更新到了现在。
每次openai出了新版本我都会直接在这里升级继续用,现在它已经成为我工作中不可或缺的重要工具,我觉得它比任何平台的AI工具都顺手,比原生的chatgpt更方便也更便宜。
此项目的初版代码其实是采用了大佬陈皓(左耳朵耗子)写的一版玩具,当时chatgpt刚出来不久,我就是看到他写的discord机器人才知道了原来discord可以这么玩,我也是从那时候才开始学习golang的。
从各种意义上来讲皓哥都是我心中的人生导师,我一直很爱看他的推文和博客,很欣赏他的观点和为人。可惜皓哥英年早逝,无法再继续见证这个波澜壮阔的AI时代了。
在这里我要向他的初版代码致敬!如果没有他这一版代码就不会有我这个项目,我也就不会学习golang,也就不会写出这么好玩的工具!
代码内容我直接贴出来:
// Package main
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/signal"
"syscall"
"github.com/bwmarrin/discordgo"
"gopkg.in/yaml.v3"
)
// Token is the token for the discord bot and chatgpt
type Token struct {
Discord string `yaml:"discord"`
ChatGPT string `yaml:"chatgpt"`
}
var token Token
// ReadConfig reads the config file and unmarshals it into the config variable
func ReadConfig() error {
fmt.Println("Reading config file...")
file, err := ioutil.ReadFile("./config.yaml")
if err != nil {
fmt.Println(err.Error())
return err
}
err = yaml.Unmarshal(file, &token)
if err != nil {
fmt.Println(err.Error())
return err
}
fmt.Println("Config file read successfully!")
return nil
}
// Start starts the bot
func Start() {
dg, err := discordgo.New("Bot " + token.Discord)
if err != nil {
fmt.Println("error creating Discord session,", err)
return
}
// Register the messageCreate func as a callback for MessageCreate events.
dg.AddHandler(messageHandler)
// In this example, we only care about receiving message events.
dg.Identify.Intents = discordgo.IntentsGuildMessages
err = dg.Open()
if err != nil {
fmt.Println("error opening connection,", err)
return
}
// Wait here until CTRL-C or other term signal is received.
fmt.Println("Bot is now running. Press CTRL-C to exit.")
done := make(chan os.Signal, 1)
signal.Notify(done, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
select {
case <-done:
fmt.Println("Received the exit signal, exiting...")
}
// Cleanly close down the Discord session.
fmt.Println("Closing Discord session...")
dg.Close()
}
func messageHandler(s *discordgo.Session, m *discordgo.MessageCreate) {
// Ignore all messages created by the bot itself
// This isn't required in this specific example but it's a good practice.
if m.Author.ID == s.State.User.ID {
return
}
// Ignore all messages that don't mention the bot
mentiond := false
for _, u := range m.Mentions {
if u.ID == s.State.User.ID {
mentiond = true
break
}
}
if !mentiond {
fmt.Printf("Not mentioned in message: [%s] %s\n", m.Author.Username, m.Content)
return
}
message := fmt.Sprintf("Message: %s, Author: %s", m.Content, m.Author.Username)
fmt.Println(message)
ChatGPTResponse, err := callChatGPT(m.Content)
if err != nil {
fmt.Println(err.Error())
s.ChannelMessageSend(m.ChannelID, err.Error())
return
}
//s.ChannelMessageSend(m.ChannelID, ChatGPTResponse)
s.ChannelMessageSendReply(m.ChannelID, ChatGPTResponse, m.Reference())
}
// ChatGPTResponse is the response from the chatgpt api
type ChatGPTResponse struct {
ID string `json:"id"`
Object string `json:"object"`
Model string `json:"model"`
Usage struct {
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
TotalTokens int `json:"total_tokens"`
} `json:"usage"`
Choices []struct {
Index int `json:"index"`
Message struct {
Role string `json:"role"`
Content string `json:"content"`
} `json:"message"`
FinishReason string `json:"finish_reason"`
} `json:"choices"`
}
func callChatGPT(msg string) (string, error) {
api := "https://api.openai.com/v1/chat/completions"
body := []byte(`{
"model": "gpt-3.5-turbo",
"messages": [
{
"role": "user",
"content": "` + JSONEscape(msg) + `"
}
]
}`)
req, err := http.NewRequest("POST", api, bytes.NewBuffer(body))
if err != nil {
fmt.Println("Error creating request", err)
return "", err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token.ChatGPT)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error sending request", err)
return "", err
}
defer resp.Body.Close()
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("Error reading response", err)
return "", err
}
chatGPTData := ChatGPTResponse{}
err = json.Unmarshal(body, &chatGPTData)
if err != nil {
fmt.Println("Error unmarshalling response", err)
return "", err
}
return chatGPTData.Choices[0].Message.Content, nil
}
// JSONEscape escape the string
func JSONEscape(str string) string {
b, err := json.Marshal(str)
if err != nil {
return str
}
s := string(b)
return s[1 : len(s)-1]
}
func main() {
err := ReadConfig()
if err != nil {
fmt.Println(err.Error())
return
}
Start()
}