diff --git a/README.md b/README.md index e8f287e..1ec915f 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,9 @@ After you download the file, extract it into a folder and open the `env.example` - Multiple IDs can be provided, separated by commas. - `EDIT_WAIT_SECONDS` (Optional): Amount of seconds to wait between edits - This is set to `1` by default, but you can increase if you start getting a lot of `Too Many Requests` errors. +- `MANUAL_AUTH` (Optional): Setting to true will disable the browser authentication + - Requires setting OpenAI Session Token manually, use `/setToken ` bot command + - See [auth section](#Authentication) for info on how to get your session token - Save the file, and rename it to `.env`. > **Note** Make sure you rename the file to _exactly_ `.env`! The program won't work otherwise. diff --git a/env.example b/env.example index 81e2546..6679680 100644 --- a/env.example +++ b/env.example @@ -1,3 +1,4 @@ TELEGRAM_ID= TELEGRAM_TOKEN= EDIT_WAIT_SECONDS=1 +MANUAL_AUTH=false \ No newline at end of file diff --git a/main.go b/main.go index ccc1e77..79449b3 100644 --- a/main.go +++ b/main.go @@ -14,13 +14,27 @@ import ( "github.com/m1guelpf/chatgpt-telegram/src/tgbot" ) +const ( + messageStart = "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)." + messageHelp = `/reload - clear chatGPT conversation history (Telegram messages will not be deleted) +/setToken - set the openAI session token` +) + func main() { + envConfig, err := config.LoadEnvConfig(".env") + if err != nil { + log.Fatalf("Couldn't load .env config: %v", err) + } + if err := envConfig.ValidateWithDefaults(); err != nil { + log.Fatalf("Invalid .env config: %v", err) + } + persistentConfig, err := config.LoadOrCreatePersistentConfig() if err != nil { log.Fatalf("Couldn't load config: %v", err) } - if persistentConfig.OpenAISession == "" { + if persistentConfig.OpenAISession == "" && !envConfig.ManualAuth { token, err := session.GetSession() if err != nil { log.Fatalf("Couldn't get OpenAI session: %v", err) @@ -34,14 +48,6 @@ func main() { chatGPT := chatgpt.Init(persistentConfig) log.Println("Started ChatGPT") - envConfig, err := config.LoadEnvConfig(".env") - if err != nil { - log.Fatalf("Couldn't load .env config: %v", err) - } - if err := envConfig.ValidateWithDefaults(); err != nil { - log.Fatalf("Invalid .env config: %v", err) - } - bot, err := tgbot.New(envConfig.TelegramToken, time.Duration(envConfig.EditWaitSeconds)) if err != nil { log.Fatalf("Couldn't start Telegram bot: %v", err) @@ -90,9 +96,20 @@ func main() { var text string switch update.Message.Command() { case "help": - 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)." + text = messageHelp case "start": - 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)." + text = messageStart + case "setToken": + token := update.Message.CommandArguments() + if token == "" { + text = "Please provide a token. Example: /setToken eyJhB..." + break + } + if err := persistentConfig.SetSessionToken(token); err != nil { + text = fmt.Sprintf("Error: %v", err) + break + } + text = "Token set successfully." case "reload": chatGPT.ResetConversation(updateChatID) text = "Started a new conversation. Enjoy!" diff --git a/src/chatgpt/chatgpt.go b/src/chatgpt/chatgpt.go index 20a7ad5..6ca886b 100644 --- a/src/chatgpt/chatgpt.go +++ b/src/chatgpt/chatgpt.go @@ -8,7 +8,6 @@ import ( "net/http" "time" - "github.com/m1guelpf/chatgpt-telegram/src/config" "github.com/m1guelpf/chatgpt-telegram/src/expirymap" "github.com/m1guelpf/chatgpt-telegram/src/sse" ) @@ -21,8 +20,12 @@ type Conversation struct { LastMessageID string } +type Config interface { + GetSessionToken() string +} + type ChatGPT struct { - SessionToken string + cfg Config AccessTokenMap expirymap.ExpiryMap conversations map[int64]Conversation } @@ -48,10 +51,10 @@ type ChatResponse struct { Message string } -func Init(config *config.Config) *ChatGPT { +func Init(config Config) *ChatGPT { return &ChatGPT{ + cfg: config, AccessTokenMap: expirymap.New(), - SessionToken: config.OpenAISession, conversations: make(map[int64]Conversation), } } @@ -123,6 +126,11 @@ func (c *ChatGPT) SendMessage(message string, tgChatID int64) (chan ChatResponse } func (c *ChatGPT) refreshAccessToken() (string, error) { + sessionToken := c.cfg.GetSessionToken() + if sessionToken == "" { + return "", errors.New("no session token, use /setToken command to set") + } + cachedAccessToken, ok := c.AccessTokenMap.Get(KEY_ACCESS_TOKEN) if ok { return cachedAccessToken, nil @@ -134,7 +142,7 @@ func (c *ChatGPT) refreshAccessToken() (string, error) { } req.Header.Set("User-Agent", USER_AGENT) - req.Header.Set("Cookie", fmt.Sprintf("__Secure-next-auth.session-token=%s", c.SessionToken)) + req.Header.Set("Cookie", fmt.Sprintf("__Secure-next-auth.session-token=%s", sessionToken)) res, err := http.DefaultClient.Do(req) if err != nil { diff --git a/src/config/config.go b/src/config/config.go index b74e52b..bcae60a 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -14,6 +14,10 @@ type Config struct { OpenAISession string } +func (cfg *Config) GetSessionToken() string { + return cfg.OpenAISession +} + // LoadOrCreatePersistentConfig uses the default config directory for the current OS // to load or create a config file named "chatgpt.json" func LoadOrCreatePersistentConfig() (*Config, error) { diff --git a/src/config/config_test.go b/src/config/config_test.go index c6f5888..2772d68 100644 --- a/src/config/config_test.go +++ b/src/config/config_test.go @@ -44,13 +44,12 @@ func TestLoadEnvConfig(t *testing.T) { want *EnvConfig }{ "all values empty in file and env": { - fileContent: `TELEGRAM_ID= -TELEGRAM_TOKEN= -EDIT_WAIT_SECONDS=`, + fileContent: emptyConfig, want: &EnvConfig{ TelegramID: []int64{}, TelegramToken: "", EditWaitSeconds: 0, + ManualAuth: false, }, }, "no file, all values through env": { @@ -58,21 +57,25 @@ EDIT_WAIT_SECONDS=`, "TELEGRAM_ID": "123,456", "TELEGRAM_TOKEN": "token", "EDIT_WAIT_SECONDS": "10", + "MANUAL_AUTH": "true", }, want: &EnvConfig{ TelegramID: []int64{123, 456}, TelegramToken: "token", EditWaitSeconds: 10, + ManualAuth: true, }, }, "all values provided in file, single TELEGRAM_ID": { fileContent: `TELEGRAM_ID=123 TELEGRAM_TOKEN=abc -EDIT_WAIT_SECONDS=10`, +EDIT_WAIT_SECONDS=10 +MANUAL_AUTH=true`, want: &EnvConfig{ TelegramID: []int64{123}, TelegramToken: "abc", EditWaitSeconds: 10, + ManualAuth: true, }, }, "multiple TELEGRAM_IDs provided in file": { @@ -89,16 +92,19 @@ EDIT_WAIT_SECONDS=10`, "env variables should override file values": { fileContent: `TELEGRAM_ID=123 TELEGRAM_TOKEN=abc -EDIT_WAIT_SECONDS=10`, +EDIT_WAIT_SECONDS=10 +MANUAL_AUTH=false`, envVars: map[string]string{ "TELEGRAM_ID": "456", "TELEGRAM_TOKEN": "def", "EDIT_WAIT_SECONDS": "20", + "MANUAL_AUTH": "true", }, want: &EnvConfig{ TelegramID: []int64{456}, TelegramToken: "def", EditWaitSeconds: 20, + ManualAuth: true, }, }, "multiple TELEGRAM_IDs provided in env": { diff --git a/src/config/env_config.go b/src/config/env_config.go index 26a7485..c1c5c96 100644 --- a/src/config/env_config.go +++ b/src/config/env_config.go @@ -13,13 +13,15 @@ type EnvConfig struct { TelegramID []int64 `mapstructure:"TELEGRAM_ID"` TelegramToken string `mapstructure:"TELEGRAM_TOKEN"` EditWaitSeconds int `mapstructure:"EDIT_WAIT_SECONDS"` + ManualAuth bool `mapstructure:"MANUAL_AUTH"` } // emptyConfig is used to initialize viper. // It is required to register config keys with viper when in case no config file is provided. const emptyConfig = `TELEGRAM_ID= TELEGRAM_TOKEN= -EDIT_WAIT_SECONDS=` +EDIT_WAIT_SECONDS= +MANUAL_AUTH=` func (e *EnvConfig) HasTelegramID(id int64) bool { for _, v := range e.TelegramID { @@ -46,7 +48,7 @@ func LoadEnvConfig(path string) (*EnvConfig, error) { } if fileExists { v.SetConfigFile(path) - if err := v.ReadInConfig(); err != nil { + if err := v.MergeInConfig(); err != nil { return nil, err } } @@ -72,7 +74,7 @@ func (e *EnvConfig) ValidateWithDefaults() error { if len(e.TelegramID) == 0 { log.Printf("TELEGRAM_ID is not set, all users will be able to use the bot") } - if e.EditWaitSeconds < 0 { + if e.EditWaitSeconds <= 0 { log.Printf("EDIT_WAIT_SECONDS not set, defaulting to 1") e.EditWaitSeconds = 1 }