From 7fbb5d46da4372a354a71ef466e2e4f32b116a4d Mon Sep 17 00:00:00 2001 From: DG Date: Fri, 2 Dec 2022 09:53:46 -0800 Subject: [PATCH] initial --- README.md | 3 ++ go.mod | 16 ++++++++ go.sum | 27 ++++++++++++++ main.go | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ server.py | 61 ++++++++++++++++++++++++++++++ 5 files changed, 217 insertions(+) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 server.py diff --git a/README.md b/README.md index 1a89bae5..7a66e526 100644 --- a/README.md +++ b/README.md @@ -1 +1,4 @@ # whatsapp-gpt +* You'll need to run WhatsApp from a phone number using the golang library I'm using. +* You'll run a dedicated browser in another window that's controlling ChatGPT. +* This this the end of the readme file for now; it is a bit sparse. Will add more later. diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..672c8a1b --- /dev/null +++ b/go.mod @@ -0,0 +1,16 @@ +module example.com/m + +go 1.19 + +require ( + filippo.io/edwards25519 v1.0.0 // indirect + github.com/gorilla/websocket v1.5.0 // indirect + github.com/mattn/go-sqlite3 v1.14.16 // indirect + github.com/mdp/qrterminal v1.0.1 // indirect + github.com/mdp/qrterminal/v3 v3.0.0 // indirect + go.mau.fi/libsignal v0.0.0-20221015105917-d970e7c3c9cf // indirect + go.mau.fi/whatsmeow v0.0.0-20221202110551-e067ee7293b0 // indirect + golang.org/x/crypto v0.0.0-20221012134737-56aed061732a // indirect + google.golang.org/protobuf v1.28.1 // indirect + rsc.io/qr v0.2.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..ae3e1665 --- /dev/null +++ b/go.sum @@ -0,0 +1,27 @@ +filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= +filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mdp/qrterminal v1.0.1 h1:07+fzVDlPuBlXS8tB0ktTAyf+Lp1j2+2zK3fBOL5b7c= +github.com/mdp/qrterminal v1.0.1/go.mod h1:Z33WhxQe9B6CdW37HaVqcRKzP+kByF3q/qLxOGe12xQ= +github.com/mdp/qrterminal/v3 v3.0.0 h1:ywQqLRBXWTktytQNDKFjhAvoGkLVN3J2tAFZ0kMd9xQ= +github.com/mdp/qrterminal/v3 v3.0.0/go.mod h1:NJpfAs7OAm77Dy8EkWrtE4aq+cE6McoLXlBqXQEwvE0= +go.mau.fi/libsignal v0.0.0-20221015105917-d970e7c3c9cf h1:mzPxXBgDPHKDHMVV1tIWh7lwCiRpzCsXC0gNRX+K07c= +go.mau.fi/libsignal v0.0.0-20221015105917-d970e7c3c9cf/go.mod h1:XCjaU93vl71YNRPn059jMrK0xRDwVO5gKbxoPxow9mQ= +go.mau.fi/whatsmeow v0.0.0-20221202110551-e067ee7293b0 h1:danzDOlj/KiDi8kNsaHOhwJ7IZdo7V7hXelkZXhJhsc= +go.mau.fi/whatsmeow v0.0.0-20221202110551-e067ee7293b0/go.mod h1:2yweL8nczvtlIxkrvCb0y8xiO13rveX9lJPambwYV/E= +golang.org/x/crypto v0.0.0-20221012134737-56aed061732a h1:NmSIgad6KjE6VvHciPZuNRTKxGhlPfD6OA87W/PLkqg= +golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY= +rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs= diff --git a/main.go b/main.go new file mode 100644 index 00000000..5165e10f --- /dev/null +++ b/main.go @@ -0,0 +1,110 @@ +package main + +import ( + "bytes" + "context" + "fmt" + "net/http" + "net/url" + "os" + "os/signal" + "syscall" + + _ "github.com/mattn/go-sqlite3" + "github.com/mdp/qrterminal/v3" + "go.mau.fi/whatsmeow" + waProto "go.mau.fi/whatsmeow/binary/proto" + "go.mau.fi/whatsmeow/store/sqlstore" + "go.mau.fi/whatsmeow/types" + "go.mau.fi/whatsmeow/types/events" + waLog "go.mau.fi/whatsmeow/util/log" + "google.golang.org/protobuf/proto" +) + +type MyClient struct { + WAClient *whatsmeow.Client + eventHandlerID uint32 +} + +func (mycli *MyClient) register() { + mycli.eventHandlerID = mycli.WAClient.AddEventHandler(mycli.eventHandler) +} + +func (mycli *MyClient) eventHandler(evt interface{}) { + switch v := evt.(type) { + case *events.Message: + newMessage := v.Message + msg := newMessage.GetExtendedTextMessage().GetText() + fmt.Println("Message received:", msg) + // Make a http request to localhost:5001/chat?q= with the message, and send the response + // URL encode the message + urlEncoded := url.QueryEscape(msg) + // Make the request + resp, err := http.Get("http://localhost:5001/chat?q=" + urlEncoded) + if err != nil { + fmt.Println("Error making request:", err) + return + } + // Read the response + buf := new(bytes.Buffer) + buf.ReadFrom(resp.Body) + newMsg := buf.String() + // encode out as a string + response := &waProto.Message{Conversation: proto.String(string(newMsg))} + userJid := types.NewJID(v.Info.Sender.User, types.DefaultUserServer) + mycli.WAClient.SendMessage(context.Background(), userJid, "", response) + + } +} + +func main() { + dbLog := waLog.Stdout("Database", "DEBUG", true) + // Make sure you add appropriate DB connector imports, e.g. github.com/mattn/go-sqlite3 for SQLite + container, err := sqlstore.New("sqlite3", "file:examplestore.db?_foreign_keys=on", dbLog) + if err != nil { + panic(err) + } + // If you want multiple sessions, remember their JIDs and use .GetDevice(jid) or .GetAllDevices() instead. + deviceStore, err := container.GetFirstDevice() + if err != nil { + panic(err) + } + clientLog := waLog.Stdout("Client", "DEBUG", true) + client := whatsmeow.NewClient(deviceStore, clientLog) + // add the eventHandler + mycli := &MyClient{WAClient: client} + mycli.register() + + if client.Store.ID == nil { + // No ID stored, new login + qrChan, _ := client.GetQRChannel(context.Background()) + err = client.Connect() + if err != nil { + panic(err) + } + for evt := range qrChan { + if evt.Event == "code" { + // Render the QR code here + // e.g. qrterminal.GenerateHalfBlock(evt.Code, qrterminal.L, os.Stdout) + // or just manually `echo 2@... | qrencode -t ansiutf8` in a terminal + qrterminal.GenerateHalfBlock(evt.Code, qrterminal.L, os.Stdout) + // fmt.Println("QR code:", evt.Code) + } else { + fmt.Println("Login event:", evt.Event) + } + } + } else { + // Already logged in, just connect + err = client.Connect() + if err != nil { + panic(err) + } + } + + // Listen to Ctrl+C (you can also do something else that prevents the program from exiting) + c := make(chan os.Signal) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + <-c + + client.Disconnect() +} diff --git a/server.py b/server.py new file mode 100644 index 00000000..ac439171 --- /dev/null +++ b/server.py @@ -0,0 +1,61 @@ +"""Make some requests to OpenAI's chatbot""" + +import time +import os +import flask + +from flask import g + +from playwright.sync_api import sync_playwright + +APP = flask.Flask(__name__) +PLAY = sync_playwright().start() +BROWSER = PLAY.chromium.launch_persistent_context( + user_data_dir="/tmp/playwright", + headless=False, +) +PAGE = BROWSER.new_page() + +def get_input_box(): + """Get the child textarea of `PromptTextarea__TextareaWrapper`""" + return PAGE.query_selector("div[class*='PromptTextarea__TextareaWrapper']").query_selector("textarea") + +def is_logged_in(): + # See if we have a textarea with data-id="root" + return get_input_box() is not None + +def send_message(message): + # Send the message + box = get_input_box() + box.click() + box.fill(message) + box.press("Enter") + +def get_last_message(): + """Get the latest message""" + page_elements = PAGE.query_selector_all("div[class*='ConversationItem__Message']") + last_element = page_elements[-1] + return last_element.inner_text() + +@APP.route("/chat", methods=["GET"]) +def chat(): + message = flask.request.args.get("q") + print("Sending message: ", message) + send_message(message) + time.sleep(10) # TODO: there are about ten million ways to be smarter than this + response = get_last_message() + print("Response: ", response) + return response + +def start_browser(): + PAGE.goto("https://chat.openai.com/") + if not is_logged_in(): + print("Please log in to OpenAI Chat") + print("Press enter when you're done") + input() + else: + print("Logged in") + APP.run(port=5001, threaded=False) + +if __name__ == "__main__": + start_browser() \ No newline at end of file