Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
m1guelpf committed Dec 8, 2022
1 parent 8326b88 commit 3da7c72
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 127 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ build:
go build

clean:
rm chatgpt-telegram
rm chatgpt-discord
22 changes: 10 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
# ChatGPT-bot
# ChatGPT Discord Bot

> Interact with ChatGPT
Go CLI to fuels a Telegram bot that lets you interact with [ChatGPT](https://openai.com/blog/chatgpt/), a large language model trained by OpenAI.
Go CLI to power a Discord bot letting you interact with [ChatGPT](https://openai.com/blog/chatgpt/), a large language model trained by OpenAI, collaboratively in Discord servers or privately in your DMs.

## Installation

Download the file corresponding to your OS in the [releases page](https://github.com/m1guelpf/chatgpt-telegram/releases/latest):
Download the file corresponding to your OS in the [releases page](https://github.com/m1guelpf/chatgpt-discord/releases/latest):

- `chatgpt-telegram-Darwin-amd64`: macOS (Intel)
- `chatgpt-telegram-Darwin-arm64`: macOS (M1)
- `chatgpt-telegram-Linux-amd64`: Linux
- `chatgpt-telegram-Linux-arm64`: Linux (ARM)
- `chatgpt-telegram-Win-amd64`: Windows
- `chatgpt-discord-Darwin-amd64`: macOS (Intel)
- `chatgpt-discord-Darwin-arm64`: macOS (M1)
- `chatgpt-discord-Linux-amd64`: Linux
- `chatgpt-discord-Linux-arm64`: Linux (ARM)
- `chatgpt-discord-Win-amd64`: Windows

After you download the file, extract it into a folder and open the `env.example` file with a text editor and fill in your credentials. You'll need your bot token, which you can find [here](https://core.telegram.org/bots/tutorial#obtain-your-bot-token), and optionally your telegram id, which you can find by DMing `@userinfobot` on Telegram. Save the file, and rename it to `.env`.
After you download the file, extract it into a folder and open the `env.example` file with a text editor and fill in your credentials. You'll need your bot token, which you can find [here](https://www.writebots.com/discord-bot-token/), and optionally (if you want to prevent anyone else from using the bot) your Discord username. Save the file, and rename it to `.env`.

> **Note** Make sure you rename the file to _exactly_ `.env`! The program won't work otherwise.
Finally, open the terminal in your computer (if you're on windows, look for `PowerShell`), navigate to the path you extracted the above file (you can use `cd dirname` to navigate to a directory, ask ChatGPT if you need more assistance 😉) and run `./chatgpt-telegram`.
Finally, open the terminal in your computer (if you're on windows, look for `PowerShell`), navigate to the path you extracted the above file (you can use `cd dirname` to navigate to a directory, ask ChatGPT if you need more assistance 😉) and run `./chatgpt-discord`.

## Authentication

Expand Down
4 changes: 2 additions & 2 deletions env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
TELEGRAM_ID=
TELEGRAM_TOKEN=
DISCORD_TOKEN=
DISCORD_USERNAME=
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
module github.com/m1guelpf/chatgpt-telegram
module github.com/m1guelpf/chatgpt-discord

go 1.19

require (
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
github.com/bwmarrin/discordgo v0.26.1
github.com/google/uuid v1.3.0
github.com/joho/godotenv v1.4.0
github.com/launchdarkly/eventsource v1.7.1
Expand All @@ -15,6 +15,7 @@ require (
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-stack/stack v1.8.1 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
Expand All @@ -25,6 +26,7 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.1 // indirect
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect
golang.org/x/text v0.4.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
Expand Down
7 changes: 5 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/bwmarrin/discordgo v0.26.1 h1:AIrM+g3cl+iYBr4yBxCBp9tD9jR3K7upEjl0d89FRkE=
github.com/bwmarrin/discordgo v0.26.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
Expand Down Expand Up @@ -65,8 +67,6 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
Expand Down Expand Up @@ -124,6 +124,7 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/h2non/filetype v1.1.1/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
Expand Down Expand Up @@ -203,6 +204,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
Expand Down
231 changes: 127 additions & 104 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@ import (
"log"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"time"

tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/bwmarrin/discordgo"
"github.com/joho/godotenv"
"github.com/m1guelpf/chatgpt-telegram/src/chatgpt"
"github.com/m1guelpf/chatgpt-telegram/src/config"
"github.com/m1guelpf/chatgpt-telegram/src/markdown"
"github.com/m1guelpf/chatgpt-telegram/src/ratelimit"
"github.com/m1guelpf/chatgpt-telegram/src/session"
"github.com/m1guelpf/chatgpt-discord/src/auth"
"github.com/m1guelpf/chatgpt-discord/src/chatgpt"
"github.com/m1guelpf/chatgpt-discord/src/config"
"github.com/m1guelpf/chatgpt-discord/src/markdown"
"github.com/m1guelpf/chatgpt-discord/src/ratelimit"
"github.com/m1guelpf/chatgpt-discord/src/ref"
"github.com/m1guelpf/chatgpt-discord/src/session"
)

type Conversation struct {
Expand Down Expand Up @@ -49,138 +51,159 @@ func main() {
log.Fatalf("Couldn't load .env file: %v", err)
}

bot, err := tgbotapi.NewBotAPI(os.Getenv("TELEGRAM_TOKEN"))
discord, err := discordgo.New("Bot " + os.Getenv("DISCORD_TOKEN"))
if err != nil {
log.Fatalf("Couldn't start Telegram bot: %v", err)
log.Fatalf("Couldn't start Discord bot: %v", err)
}

c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
bot.StopReceivingUpdates()
discord.Close()
os.Exit(0)
}()

updateConfig := tgbotapi.NewUpdate(0)
updateConfig.Timeout = 30
updates := bot.GetUpdatesChan(updateConfig)
userConversations := make(map[string]Conversation)

log.Printf("Started Telegram bot! Message @%s to start.", bot.Self.UserName)
// get app id
app, err := discord.Application("@me")
if err != nil {
log.Fatalf("Couldn't get app id: %v", err)
}

_, err = discord.ApplicationCommandCreate(app.ID, os.Getenv("DISCORD_GUILD_ID"), &discordgo.ApplicationCommand{
Name: "reload",
Description: "Start a new conversation.",
DMPermission: ref.Of(true),
})
if err != nil {
log.Fatalf("Couldn't create reload command: %v", err)
}

discord.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) {
if i.Type != discordgo.InteractionApplicationCommand {
return
}

userConversations := make(map[int64]Conversation)
if !auth.CanInteract(i.Member.User) {
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "You are not authorized to use this bot.",
},
})
if err != nil {
log.Printf("Couldn't send message: %v", err)
}
return
}

for update := range updates {
if update.Message == nil {
continue
if i.ApplicationCommandData().Name == "reload" {
userConversations[i.ChannelID] = Conversation{}
err = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Started a new conversation. Enjoy!",
},
})
}
})

msg := tgbotapi.NewMessage(update.Message.Chat.ID, "")
msg.ReplyToMessageID = update.Message.MessageID
msg.ParseMode = "Markdown"
discord.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) {
if m.Author.ID == s.State.User.ID {
return
}

userId := strconv.FormatInt(update.Message.Chat.ID, 10)
if os.Getenv("TELEGRAM_ID") != "" && userId != os.Getenv("TELEGRAM_ID") {
msg.Text = "You are not authorized to use this bot."
bot.Send(msg)
continue
if m.GuildID != "" && ((len(m.Mentions) == 0 || m.Mentions[0].ID != s.State.User.ID) || (m.ReferencedMessage != nil && m.ReferencedMessage.Author.ID != s.State.User.ID)) {
return
}

bot.Request(tgbotapi.NewChatAction(update.Message.Chat.ID, "typing"))
if !update.Message.IsCommand() {
feed, err := chatGPT.SendMessage(update.Message.Text, userConversations[update.Message.Chat.ID].ConversationID, userConversations[update.Message.Chat.ID].LastMessageID)
if !auth.CanInteract(m.Author) {
_, err := s.ChannelMessageSendReply(m.ChannelID, "You are not authorized to use this bot.", &discordgo.MessageReference{MessageID: m.ID})
if err != nil {
msg.Text = fmt.Sprintf("Error: %v", err)
log.Printf("Couldn't send message: %v", err)
}
return
}

var message tgbotapi.Message
var lastResp string
query := strings.TrimSpace(strings.ReplaceAll(m.Content, fmt.Sprintf("<@%s>", s.State.User.ID), ""))

debouncedType := ratelimit.Debounce((10 * time.Second), func() {
bot.Request(tgbotapi.NewChatAction(update.Message.Chat.ID, "typing"))
})
debouncedEdit := ratelimit.DebounceWithArgs((1 * time.Second), func(text interface{}, messageId interface{}) {
_, err = bot.Request(tgbotapi.EditMessageTextConfig{
BaseEdit: tgbotapi.BaseEdit{
ChatID: msg.ChatID,
MessageID: messageId.(int),
},
Text: text.(string),
ParseMode: "Markdown",
})

if err != nil {
if err.Error() == "Bad Request: message is not modified: specified new message content and reply markup are exactly the same as a current content and reply markup of the message" {
return
}
feed, err := chatGPT.SendMessage(query, userConversations[m.ChannelID].ConversationID, userConversations[m.ChannelID].LastMessageID)
if err != nil {
_, err = s.ChannelMessageSendReply(m.ChannelID, fmt.Sprintf("Couldn't send message: %v", err), &discordgo.MessageReference{MessageID: m.ID})
if err != nil {
log.Printf("Couldn't send message: %v", err)
}
}

log.Printf("Couldn't edit message: %v", err)
}
})
err = s.ChannelTyping(m.ChannelID)
if err != nil {
log.Printf("Couldn't start typing: %v", err)
}

pollResponse:
for {
debouncedType()
var msg discordgo.Message
var lastResp string

select {
case response, ok := <-feed:
if !ok {
break pollResponse
}
debouncedType := ratelimit.Debounce((10 * time.Second), func() {
err = s.ChannelTyping(m.ChannelID)
if err != nil {
log.Printf("Couldn't start typing: %v", err)
}
})

userConversations[update.Message.Chat.ID] = Conversation{
LastMessageID: response.MessageId,
ConversationID: response.ConversationId,
}
lastResp = markdown.EnsureFormatting(response.Message)
msg.Text = lastResp

if message.MessageID == 0 {
message, err = bot.Send(msg)
if err != nil {
log.Fatalf("Couldn't send message: %v", err)
}
} else {
debouncedEdit(lastResp, message.MessageID)
}
}
debouncedEdit := ratelimit.DebounceWithArgs((1 * time.Second), func(text interface{}, messageId interface{}) {
_, err := s.ChannelMessageEdit(m.ChannelID, messageId.(string), text.(string))
if err != nil {
log.Printf("Couldn't edit message: %v", err)
}
})

pollResponse:
for {
select {
case response, ok := <-feed:
if !ok {
break pollResponse
}

_, err = bot.Request(tgbotapi.EditMessageTextConfig{
BaseEdit: tgbotapi.BaseEdit{
ChatID: msg.ChatID,
MessageID: message.MessageID,
},
Text: lastResp,
ParseMode: "Markdown",
})
userConversations[m.ChannelID] = Conversation{
LastMessageID: response.MessageId,
ConversationID: response.ConversationId,
}

if err != nil {
if err.Error() == "Bad Request: message is not modified: specified new message content and reply markup are exactly the same as a current content and reply markup of the message" {
continue
lastResp = markdown.EnsureFormatting(response.Message)

if msg.ID == "" {
_msg, err := s.ChannelMessageSendReply(m.ChannelID, lastResp, &discordgo.MessageReference{MessageID: m.ID})
if err != nil {
log.Printf("Couldn't send message: %v", err)
}
msg = *_msg
} else {
debouncedEdit(lastResp, msg.ID)
}

log.Printf("Couldn't perform final edit on message: %v", err)
debouncedType()
}

continue
}

switch update.Message.Command() {
case "help":
msg.Text = "Send a message to start talking with ChatGPT. You can use /reload at any point to clear the conversation history and start from scratch (don't worry, it won't delete the Telegram messages)."
case "start":
msg.Text = "Send a message to start talking with ChatGPT. You can use /reload at any point to clear the conversation history and start from scratch (don't worry, it won't delete the Telegram messages)."
case "reload":
userConversations[update.Message.Chat.ID] = Conversation{}
msg.Text = "Started a new conversation. Enjoy!"
default:
continue
_, err = s.ChannelMessageEdit(m.ChannelID, msg.ID, lastResp)
if err != nil {
log.Printf("Couldn't perform final edit: %v", err)
}
})

if _, err := bot.Send(msg); err != nil {
log.Printf("Couldn't send message: %v", err)
continue
}
discord.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) {
log.Println("Started Discord bot.")
log.Printf("Add this bot to your server here: https://discord.com/api/oauth2/authorize?client_id=%s&permissions=2147484672&scope=bot%sapplications.commands", app.ID, "%20")
})

// start the bot
err = discord.Open()
if err != nil {
log.Fatalf("Couldn't start Discord bot: %v", err)
}

<-make(chan struct{})
}
Loading

0 comments on commit 3da7c72

Please sign in to comment.