From 4bddc569a476b9cff7c269cf880b10a7e1b795e9 Mon Sep 17 00:00:00 2001 From: Cameron Reikes Date: Mon, 27 Mar 2023 18:21:09 -0700 Subject: [PATCH] Somewhat working payment system without saving --- .gitignore | 1 + build_web_debug.bat | 4 +- build_web_release.bat | 6 +- main.c | 24 ++- marketing_page/favicon.ico | Bin 0 -> 16958 bytes marketing_page/index.html | 1 + run_codegen.bat | 1 + server/go.mod | 11 +- server/go.sum | 35 ++++ server/main.go | 345 +++++++++++++++++++++++++++++++++++-- todo.txt | 1 + web_template.html | 297 +++++++++++++++++++++++-------- 12 files changed, 641 insertions(+), 85 deletions(-) create mode 100644 marketing_page/favicon.ico diff --git a/.gitignore b/.gitignore index f53e225..5859cfb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Server builds server/rpgpt +server/*.db # sensitive API_KEY.bat diff --git a/build_web_debug.bat b/build_web_debug.bat index fa498fd..81d0a09 100644 --- a/build_web_debug.bat +++ b/build_web_debug.bat @@ -9,8 +9,10 @@ call run_codegen.bat || goto :error @REM GO FUCK YOURSELF set FLAGS=-s TOTAL_STACK=5242880 +copy marketing_page\favicon.ico build_web\favicon.ico + @echo on -emcc -sEXPORTED_FUNCTIONS=_main,_end_text_input -sEXPORTED_RUNTIME_METHODS=ccall,cwrap -O0 -s ALLOW_MEMORY_GROWTH %FLAGS% --source-map-base ../ -gsource-map -DDEVTOOLS -Ithirdparty -Igen main.c -o build_web\index.html --preload-file assets --shell-file web_template.html || goto :error +emcc -sEXPORTED_FUNCTIONS=_main,_end_text_input,_stop_controlling_input,_start_controlling_input -sEXPORTED_RUNTIME_METHODS=ccall,cwrap -O0 -s ALLOW_MEMORY_GROWTH %FLAGS% --source-map-base ../ -gsource-map -DDEVTOOLS -Ithirdparty -Igen main.c -o build_web\index.html --preload-file assets --shell-file web_template.html || goto :error @echo off goto :EOF diff --git a/build_web_release.bat b/build_web_release.bat index 527ead8..8a9934f 100644 --- a/build_web_release.bat +++ b/build_web_release.bat @@ -8,8 +8,12 @@ call run_codegen.bat || goto :error @REM GO FUCK YOURSELF set FLAGS=-s TOTAL_STACK=5242880 +copy marketing_page\favicon.ico build_web_release\favicon.ico +copy marketing_page\eye_closed.svg build_web_release\eye_closed.svg +copy marketing_page\eye_open.svg build_web_release\eye_open.svg + echo Building release -emcc -sEXPORTED_FUNCTIONS=_main,_end_text_input -sEXPORTED_RUNTIME_METHODS=ccall,cwrap -DNDEBUG -O2 -s ALLOW_MEMORY_GROWTH %FLAGS% -Ithirdparty -Igen main.c -o build_web_release\index.html --preload-file assets --shell-file web_template.html || goto :error +emcc -sEXPORTED_FUNCTIONS=_main,_end_text_input,_stop_controlling_input,_start_controlling_input -sEXPORTED_RUNTIME_METHODS=ccall,cwrap -DNDEBUG -O2 -s ALLOW_MEMORY_GROWTH %FLAGS% -Ithirdparty -Igen main.c -o build_web_release\index.html --preload-file assets --shell-file web_template.html || goto :error goto :EOF diff --git a/main.c b/main.c index a369ec8..e571d82 100644 --- a/main.c +++ b/main.c @@ -354,10 +354,19 @@ void begin_text_input() } #else #ifdef WEB -void begin_text_input() +EMSCRIPTEN_KEEPALIVE +void stop_controlling_input() { - Log("Disabling event handlers\n"); _sapp_emsc_unregister_eventhandlers(); // stop getting input, hand it off to text input +} + +EMSCRIPTEN_KEEPALIVE +void start_controlling_input() +{ + _sapp_emsc_register_eventhandlers(); +} +void begin_text_input() +{ memset(keydown, 0, ARRLEN(keydown)); emscripten_run_script("start_dialog();"); } @@ -747,9 +756,13 @@ void begin_text_input(); // called when player engages in dialog, must say somet // a callback, when 'text backend' has finished making text. End dialog void end_text_input(char *what_player_said) { + // avoid double ending text input + if(player->state != CHARACTER_TALKING) + { + return; + } player->state = CHARACTER_IDLE; #ifdef WEB // hacky - _sapp_emsc_register_eventhandlers(); #endif size_t player_said_len = strlen(what_player_said); @@ -1115,6 +1128,11 @@ void audio_stream_callback(float *buffer, int num_frames, int num_channels) void init(void) { +#ifdef WEB + EM_ASM({ + set_server_url(UTF8ToString($0)); + }, SERVER_URL); +#endif Log("Size of entity struct: %zu\n", sizeof(Entity)); Log("Size of %d entities: %zu kb\n", (int)ARRLEN(entities), sizeof(entities)/1024); sg_setup(&(sg_desc){ diff --git a/marketing_page/favicon.ico b/marketing_page/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..d3d4df62be1c81834632a9be9467b663fe8bae1a GIT binary patch literal 16958 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 + PlayGPT diff --git a/run_codegen.bat b/run_codegen.bat index a0c0b40..3575ce1 100644 --- a/run_codegen.bat +++ b/run_codegen.bat @@ -4,6 +4,7 @@ echo Asset packs which must be bought and unzipped into root directory before ru echo https://rafaelmatos.itch.io/epic-rpg-world-pack-ancient-ruins echo https://sventhole.itch.io/undead-pixel-art-characters + rmdir /S /q assets\copyrighted mkdir assets\copyrighted copy "EPIC RPG World Pack - Ancient Ruins V 1.7\EPIC RPG World Pack - Ancient Ruins V 1.7\Characters\NPC Merchant-idle.png" "assets\copyrighted\merchant.png" || goto :error diff --git a/server/go.mod b/server/go.mod index 2d1419c..1b13e21 100644 --- a/server/go.mod +++ b/server/go.mod @@ -2,4 +2,13 @@ module github.com/creikey/rpgpt go 1.19 -require github.com/sashabaranov/go-gpt3 v1.2.1 // indirect +require ( + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/mattn/go-sqlite3 v1.14.15 // indirect + github.com/sashabaranov/go-gpt3 v1.2.1 // indirect + github.com/stripe/stripe-go/v72 v72.122.0 // indirect + github.com/stripe/stripe-go/v74 v74.13.0 // indirect + gorm.io/driver/sqlite v1.4.4 // indirect + gorm.io/gorm v1.24.6 // indirect +) diff --git a/server/go.sum b/server/go.sum index db2116d..06fbc8f 100644 --- a/server/go.sum +++ b/server/go.sum @@ -1,2 +1,37 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= +github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sashabaranov/go-gpt3 v1.2.1 h1:kfU+vQ1ThI7p+xfwwJC8olEEEWjK3smgKZ3FcYbaLRQ= github.com/sashabaranov/go-gpt3 v1.2.1/go.mod h1:BIZdbwdzxZbCrcKGMGH6u2eyGe1xFuX9Anmh3tCP8lQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stripe/stripe-go/v72 v72.122.0 h1:eRXWqnEwGny6dneQ5BsxGzUCED5n180u8n665JHlut8= +github.com/stripe/stripe-go/v72 v72.122.0/go.mod h1:QwqJQtduHubZht9mek5sds9CtQcKFdsykV9ZepRWwo0= +github.com/stripe/stripe-go/v74 v74.13.0 h1:n9VIeApHaGsqRQcEsr8ANldfFrLzFSasfNBkq0roPTw= +github.com/stripe/stripe-go/v74 v74.13.0/go.mod h1:f9L6LvaXa35ja7eyvP6GQswoaIPaBRvGAimAO+udbBw= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/sqlite v1.4.4 h1:gIufGoR0dQzjkyqDyYSCvsYR6fba1Gw5YKDqKeChxFc= +gorm.io/driver/sqlite v1.4.4/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= +gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= +gorm.io/gorm v1.24.6 h1:wy98aq9oFEetsc4CAbKD2SoBCdMzsbSIvSUUFJuHi5s= +gorm.io/gorm v1.24.6/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= diff --git a/server/main.go b/server/main.go index 08147b1..3965eec 100644 --- a/server/main.go +++ b/server/main.go @@ -2,31 +2,316 @@ package main import ( "fmt" + "time" "net/http" "io" "os" "context" "log" + "io/ioutil" + "strings" + "encoding/json" + "math/rand" gogpt "github.com/sashabaranov/go-gpt3" + "github.com/stripe/stripe-go/v74" + "github.com/stripe/stripe-go/v74/webhook" + "github.com/stripe/stripe-go/v74/checkout/session" + + "gorm.io/gorm" + "gorm.io/driver/sqlite" + +) + +// BoughtType values. do not reorganize these or you fuck up the database +const ( + DayPass = iota +) + +const ( + // A-Z and 0-9, four digits means this many codes + MaxCodes = 36 * 36 * 36 * 36 ) +type userCode int + +type User struct { + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt gorm.DeletedAt `gorm:"index"` + + Code userCode `gorm:"primaryKey"` // of maximum value max codes, incremented one by one. These are converted to 4 digit alphanumeric code users can remember/use + BoughtTime int64 // unix time. Used to figure out if the pass is still valid + BoughtType int // enum + + IsFulfilled bool // before users are checked out they are unfulfilled + CheckoutSessionID string +} + var c *gogpt.Client +var logResponses = false +var doCors = false +var checkoutRedirectTo string +var daypassPriceId string +var webhookSecret string +var db *gorm.DB + +func intPow(n, m int) int { + if m == 0 { + return 1 + } + result := n + for i := 2; i <= m; i++ { + result *= n + } + return result +} + +var numberToChar = [...]rune{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'} + +func codeToString(code userCode) (string, error) { + toReturn := "" + value := int(code) + // converting to base 36 (A-Z + numbers) then into characters, then appending + for digit := 3; digit >= 0; digit-- { + currentPlaceValue := value / intPow(36, digit) + value -= currentPlaceValue * intPow(36, digit) + if currentPlaceValue >= len(numberToChar) { return "", fmt.Errorf("Failed to generate usercode %d to string, currentPlaceValue %d and length of number to char %d", code, currentPlaceValue, len(numberToChar)) } + toReturn += string(numberToChar[currentPlaceValue]) + } + return toReturn, nil; +} + +func parseUserCode(s string) (userCode, error) { + asRune := []rune(s) + if len(asRune) != 4 { return 0, fmt.Errorf("String to deconvert is not of length 4: %s", s) } + var toReturn userCode = 0 + for digit := 3; digit >= 0; digit-- { + curDigitNum := 0 + found := false + for i, letter := range numberToChar { + if letter == asRune[digit] { + curDigitNum = i + found = true + } + } + if !found { return 0, fmt.Errorf("Failed to find digit's number %s", s) } + toReturn += userCode(curDigitNum * intPow(36, digit)) + } + return toReturn, nil +} + +func isUserOld(user User) bool { + return (currentTime() - user.BoughtTime) > 24*60*60 +} + +func clearOld(db *gorm.DB) { + var users []User + result := db.Find(&users) + if result.Error != nil { + log.Fatal(result.Error) + } + var toDelete []userCode // codes + for _, user := range users { + if user.BoughtType != 0 { + panic("Don't know how to handle bought type " + string(user.BoughtType) + " yet") + } + if isUserOld(user) { + toDelete = append(toDelete, user.Code) + } + } + + for _, del := range toDelete { + db.Delete(&User{}, del) + } +} + +func webhookResponse(w http.ResponseWriter, req *http.Request) { + const MaxBodyBytes = int64(65536) + req.Body = http.MaxBytesReader(w, req.Body, MaxBodyBytes) + + body, err := ioutil.ReadAll(req.Body) + if err != nil { + log.Printf("Error reading request body: %v\n", err) + w.WriteHeader(http.StatusServiceUnavailable) + return + } + + endpointSecret := webhookSecret + event, err := webhook.ConstructEvent(body, req.Header.Get("Stripe-Signature"), endpointSecret) + + if err != nil { + log.Printf("Error verifying webhook signature %s\n", err) + w.WriteHeader(http.StatusBadRequest) // Return a 400 error on a bad signature + return + } + + if event.Type == "checkout.session.completed" { + var session stripe.CheckoutSession + err := json.Unmarshal(event.Data.Raw, &session) + if err != nil { + log.Printf("Error parsing webhook JSON %s", err) + w.WriteHeader(http.StatusBadRequest) + return + } + + params := &stripe.CheckoutSessionParams{} + params.AddExpand("line_items") + + // Retrieve the session. If you require line items in the response, you may include them by expanding line_items. + // Fulfill the purchase... + + + var toFulfill User + found := false + for trial := 0; trial < 5; trial++ { + if db.Where("checkout_session_id = ?", session.ID).First(&toFulfill).Error != nil { + log.Println("Failed to fulfill user with ID " + session.ID) + } else { + found = true + break + } + } + if !found { + log.Println("Error Failed to find user in database to fulfill: very bad! ID: " + session.ID) + } else { + userString, err := codeToString(toFulfill.Code) + if err != nil { + log.Printf("Error strange thing, saved user's code was unable to be converted to a string %s", err) + } + log.Printf("Fulfilling user with code %s\n", userString) + toFulfill.IsFulfilled = true + db.Save(&toFulfill) + } + } + + w.WriteHeader(http.StatusOK) +} + +func checkout(w http.ResponseWriter, req *http.Request) { + if doCors { + w.Header().Set("Access-Control-Allow-Origin", "*") + } + + // generate a code + var newCode string + var newCodeUser userCode + found := false + for i := 0; i < 1000; i++ { + codeInt := rand.Intn(MaxCodes) + newCodeUser = userCode(codeInt) + var tmp User + r := db.Where("Code = ?", newCodeUser).Limit(1).Find(&tmp) + if r.RowsAffected == 0{ + var err error + newCode, err = codeToString(newCodeUser) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.Fatalf("Failed to generate code from random number: %s", err) + return + } + found = true + break + } + } + if !found { + w.WriteHeader(http.StatusInternalServerError) + log.Fatal("Failed to find new code!!!") + return + } + + params := &stripe.CheckoutSessionParams { + LineItems: []*stripe.CheckoutSessionLineItemParams { + &stripe.CheckoutSessionLineItemParams{ + Price: stripe.String(daypassPriceId), + Quantity: stripe.Int64(1), + }, + }, + Mode: stripe.String(string(stripe.CheckoutSessionModePayment)), + SuccessURL: stripe.String(checkoutRedirectTo), + CancelURL: stripe.String(checkoutRedirectTo), + } + + s, err := session.New(params) + + if err != nil { + log.Printf("session.New: %v", err) + } + + log.Printf("Creating user with checkout session ID %s\n", s.ID) + result := db.Create(&User { + Code: newCodeUser, + BoughtTime: currentTime(), + BoughtType: DayPass, + IsFulfilled: false, + CheckoutSessionID: s.ID, + }) + if result.Error != nil { + w.WriteHeader(http.StatusInternalServerError) + log.Printf("Failed to write to database: %s", result.Error) + } else { + fmt.Fprintf(w, "%s|%s", newCode, s.URL) + } +} func index(w http.ResponseWriter, req *http.Request) { - //time.Sleep(4 * time.Second) req.Body = http.MaxBytesReader(w, req.Body, 1024 * 1024) // no sending huge files to crash the server - promptBytes, err := io.ReadAll(req.Body) + if doCors { + w.Header().Set("Access-Control-Allow-Origin", "*") + } + bodyBytes, err := io.ReadAll(req.Body) if err != nil { w.WriteHeader(http.StatusBadRequest) + log.Println("Bad error: ", err) return } else { - promptString := string(promptBytes) + bodyString := string(bodyBytes) + splitBody := strings.Split(bodyString, "|") + + if len(splitBody) != 2 { + w.WriteHeader(http.StatusBadRequest) + } + var promptString string = splitBody[1] + var userToken string = splitBody[0] - fmt.Println() - fmt.Println("Println line prompt string: ", promptString) + // see if need to pay + rejected := false + { + if len(userToken) != 4 { + log.Println("Rejected because not 4: `" + userToken + "`") + rejected = true + } else { + var thisUser User + thisUserCode, err := parseUserCode(userToken) + if err != nil { + log.Printf("Error: Failed to parse user token %s\n", userToken) + rejected = true + } else { + if db.First(&thisUser, thisUserCode).Error != nil { + log.Printf("User code %d string %s couldn't be found in the database: %s\n", thisUserCode, userToken, db.Error) + rejected = true + } else { + if isUserOld(thisUser) { + log.Println("User code " + userToken + " is old, not valid") + db.Delete(&thisUser) + rejected = true + } else { + rejected = false + } + } + } + } + } + if rejected { + fmt.Fprintf(w, "0") + return + } + + if logResponses { + log.Println("Println line prompt string: ", promptString) + } ctx := context.Background() - req := gogpt.CompletionRequest{ + req := gogpt.CompletionRequest { Model: "curie:ft-personal-2023-03-24-03-06-24", MaxTokens: 80, Prompt: promptString, @@ -39,26 +324,64 @@ func index(w http.ResponseWriter, req *http.Request) { } resp, err := c.CreateCompletion(ctx, req) if err != nil { - fmt.Println("Failed to generate: ", err) + log.Println("Error Failed to generate: ", err) w.WriteHeader(http.StatusInternalServerError) return } response := resp.Choices[0].Text - fmt.Println("Println response: ", response) - fmt.Fprintf(w, "%s", response) + if logResponses { + log.Println("Println response: ", response) + } + fmt.Fprintf(w, "1%s", response) } } +func currentTime() int64 { + return time.Now().Unix() +} + func main() { + var err error + db, err = gorm.Open(sqlite.Open("rpgpt.db"), &gorm.Config{}) + if err != nil { + log.Fatal(err) + } + db.AutoMigrate(&User{}) + + clearOld(db) + api_key := os.Getenv("OPENAI_API_KEY") if api_key == "" { log.Fatal("Must provide openai key") } + checkoutRedirectTo = os.Getenv("REDIRECT_TO") + if checkoutRedirectTo == "" { + log.Fatal("Must provide a base URL (without slash) for playgpt to redirect to") + } + stripeKey := os.Getenv("STRIPE_KEY") + if stripeKey == "" { + log.Fatal("Must provide stripe key") + } + daypassPriceId = os.Getenv("PRICE_ID") + if daypassPriceId == "" { + log.Fatal("Must provide daypass price ID") + } + stripe.Key = stripeKey + webhookSecret = os.Getenv("WEBHOOK_SECRET") + if webhookSecret == "" { + log.Fatal("Must provide webhook secret for receiving checkout completed events") + } + + logResponses = os.Getenv("LOG_RESPONSES") != "" + doCors = os.Getenv("CORS") != "" c = gogpt.NewClient(api_key) http.HandleFunc("/", index) + http.HandleFunc("/webhook", webhookResponse) + http.HandleFunc("/checkout", checkout) - log.Println("Serving...") - http.ListenAndServe(":8090", nil) + portString := ":8090" + log.Println("Serving on " + portString + "...") + http.ListenAndServe(portString, nil) } diff --git a/todo.txt b/todo.txt index 2bb0417..ca4d517 100644 --- a/todo.txt +++ b/todo.txt @@ -6,6 +6,7 @@ Happening by END OF STREAM: - New characters/items from fate - New art in - Old man in beginning is invincible + - Make new openai key (it was leaked) - Add cancel button - Style buttons - Make map better diff --git a/web_template.html b/web_template.html index 1d12f4c..ef3c7a7 100644 --- a/web_template.html +++ b/web_template.html @@ -1,36 +1,16 @@ - - + + + AI RPG + + - + + + +
+

AI is expensive so you need to pay for it

+

You can play for free 10 minutes per day

+
+ +

24 Hour Pass

+ +
+
+ + + +
+
@@ -109,7 +175,6 @@ body {
- {{{ SCRIPT }}}