Somewhat working payment system without saving

main
Cameron Murphy Reikes 2 years ago
parent 466e998adf
commit 4bddc569a4

1
.gitignore vendored

@ -1,5 +1,6 @@
# Server builds # Server builds
server/rpgpt server/rpgpt
server/*.db
# sensitive # sensitive
API_KEY.bat API_KEY.bat

@ -9,8 +9,10 @@ call run_codegen.bat || goto :error
@REM GO FUCK YOURSELF @REM GO FUCK YOURSELF
set FLAGS=-s TOTAL_STACK=5242880 set FLAGS=-s TOTAL_STACK=5242880
copy marketing_page\favicon.ico build_web\favicon.ico
@echo on @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 @echo off
goto :EOF goto :EOF

@ -8,8 +8,12 @@ call run_codegen.bat || goto :error
@REM GO FUCK YOURSELF @REM GO FUCK YOURSELF
set FLAGS=-s TOTAL_STACK=5242880 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 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 goto :EOF

@ -354,10 +354,19 @@ void begin_text_input()
} }
#else #else
#ifdef WEB #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 _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)); memset(keydown, 0, ARRLEN(keydown));
emscripten_run_script("start_dialog();"); 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 // a callback, when 'text backend' has finished making text. End dialog
void end_text_input(char *what_player_said) void end_text_input(char *what_player_said)
{ {
// avoid double ending text input
if(player->state != CHARACTER_TALKING)
{
return;
}
player->state = CHARACTER_IDLE; player->state = CHARACTER_IDLE;
#ifdef WEB // hacky #ifdef WEB // hacky
_sapp_emsc_register_eventhandlers();
#endif #endif
size_t player_said_len = strlen(what_player_said); 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) 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 entity struct: %zu\n", sizeof(Entity));
Log("Size of %d entities: %zu kb\n", (int)ARRLEN(entities), sizeof(entities)/1024); Log("Size of %d entities: %zu kb\n", (int)ARRLEN(entities), sizeof(entities)/1024);
sg_setup(&(sg_desc){ sg_setup(&(sg_desc){

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

@ -1,4 +1,5 @@
<html> <html>
<! SET TEH FAVICON.ICO>
<head> <head>
<title>PlayGPT</title> <title>PlayGPT</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">

@ -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://rafaelmatos.itch.io/epic-rpg-world-pack-ancient-ruins
echo https://sventhole.itch.io/undead-pixel-art-characters echo https://sventhole.itch.io/undead-pixel-art-characters
rmdir /S /q assets\copyrighted rmdir /S /q assets\copyrighted
mkdir 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 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

@ -2,4 +2,13 @@ module github.com/creikey/rpgpt
go 1.19 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
)

@ -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 h1:kfU+vQ1ThI7p+xfwwJC8olEEEWjK3smgKZ3FcYbaLRQ=
github.com/sashabaranov/go-gpt3 v1.2.1/go.mod h1:BIZdbwdzxZbCrcKGMGH6u2eyGe1xFuX9Anmh3tCP8lQ= 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=

@ -2,28 +2,313 @@ package main
import ( import (
"fmt" "fmt"
"time"
"net/http" "net/http"
"io" "io"
"os" "os"
"context" "context"
"log" "log"
"io/ioutil"
"strings"
"encoding/json"
"math/rand"
gogpt "github.com/sashabaranov/go-gpt3" 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 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) { 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 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 { if err != nil {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
log.Println("Bad error: ", err)
return return
} else { } else {
promptString := string(promptBytes) bodyString := string(bodyBytes)
splitBody := strings.Split(bodyString, "|")
fmt.Println() if len(splitBody) != 2 {
fmt.Println("Println line prompt string: ", promptString) w.WriteHeader(http.StatusBadRequest)
}
var promptString string = splitBody[1]
var userToken string = splitBody[0]
// 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() ctx := context.Background()
req := gogpt.CompletionRequest { req := gogpt.CompletionRequest {
@ -39,26 +324,64 @@ func index(w http.ResponseWriter, req *http.Request) {
} }
resp, err := c.CreateCompletion(ctx, req) resp, err := c.CreateCompletion(ctx, req)
if err != nil { if err != nil {
fmt.Println("Failed to generate: ", err) log.Println("Error Failed to generate: ", err)
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
} }
response := resp.Choices[0].Text response := resp.Choices[0].Text
fmt.Println("Println response: ", response) if logResponses {
fmt.Fprintf(w, "%s", response) log.Println("Println response: ", response)
}
fmt.Fprintf(w, "1%s", response)
} }
} }
func currentTime() int64 {
return time.Now().Unix()
}
func main() { 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") api_key := os.Getenv("OPENAI_API_KEY")
if api_key == "" { if api_key == "" {
log.Fatal("Must provide openai 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) c = gogpt.NewClient(api_key)
http.HandleFunc("/", index) http.HandleFunc("/", index)
http.HandleFunc("/webhook", webhookResponse)
http.HandleFunc("/checkout", checkout)
log.Println("Serving...") portString := ":8090"
http.ListenAndServe(":8090", nil) log.Println("Serving on " + portString + "...")
http.ListenAndServe(portString, nil)
} }

@ -6,6 +6,7 @@ Happening by END OF STREAM:
- New characters/items from fate - New characters/items from fate
- New art in - New art in
- Old man in beginning is invincible - Old man in beginning is invincible
- Make new openai key (it was leaked)
- Add cancel button - Add cancel button
- Style buttons - Style buttons
- Make map better - Make map better

@ -1,36 +1,16 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<!-- saved from url=(0055)https://floooh.github.io/sokol-html5/triangle-sapp.html --> <html lang="en">
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>AI RPG</title> <title>AI RPG</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<style type="text/css"> <style type="text/css">
body { body {
margin: 0; margin: 0;
background-color: black; background-color: black;
} }
.game-title {
pointer-events: none;
position: absolute;
bottom: 10px;
margin-top: 0px;
padding-left: 10px;
color: white;
text-decoration: none;
z-index: 1;
text-align: left;
font-family: "Arial Black", Gadget, sans-serif;
font-size: 30px;
}
.game-menu-item {
pointer-events: auto;
font-size: 18px;
padding-left: 10px;
font-family: Arial, Helvetica, sans-serif;
}
.game-menu-link {
text-decoration: none;
color: white;
}
.game { .game {
position: absolute; position: absolute;
top: 0px; top: 0px;
@ -50,7 +30,7 @@ body {
image-rendering: pixelated; image-rendering: pixelated;
-ms-interpolation-mode: nearest-neighbor; -ms-interpolation-mode: nearest-neighbor;
} }
.inputdiv { #inputdiv, #buy_menu {
position: absolute; position: absolute;
top: 0px; top: 0px;
left: 0px; left: 0px;
@ -58,12 +38,13 @@ body {
border: 0; border: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: hidden;
display: none; /* set to flex by javascript */
justify-content: center; justify-content: center;
align-items: center; align-items: center;
}
#inputdiv {
z-index: 10; z-index: 10;
display: none; /* set to flex by javascript */
overflow: hidden;
background: #00000090; background: #00000090;
} }
#inputtext { #inputtext {
@ -99,9 +80,94 @@ body {
font-size: 3em; font-size: 3em;
} }
#buy_menu {
display: none; /* set in javascript when shown when error code */
flex-direction: column;
justify-content: center;
word-wrap: break-word;
align-items: center;
text-align: center;
height: 100vh;
background: #000000D0;
font-family: arial;
gap: 20px;
color: #fff;
z-index: 20;
}
#buy_menu h1, #daypass {
margin-left: 30px;
margin-right: 30px;
}
/* Style the payment screen title */
#buy_menu h1 {
font-size: 32px;
}
/* Style the payment button */
#payment-button, #submit-button {
background-color: #fff;
color: #000;
font-size: 24px;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
}
#daypass {
border: 4px solid white;
border-radius: 25px;
padding: 10px;
display: flex;
gap: 20px;
}
#codebox {
border: 4px solid white;
border-radius: 10px;
padding: 10px;
margin: 30px;
}
#visibility-toggle, #closebutton {
border: none;
font-size: 30px;
background: none;
color: white;
}
#closebutton {
float: right;
}
/* Media queries for smaller screens */
@media screen and (max-width: 480px) {
#buy_menu h1 {
font-size: 24px;
margin-bottom: 20px;
}
#payment-button {
font-size: 20px;
padding: 8px 16px;
}
}
</style> </style>
<link id="sokol-app-favicon" rel="shortcut icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAZdJREFUOE9VUzFSw0AMlGihggekwhV0eQGZ8AQggQ4chlR5CjSeAUwHOHkCZPALoEuVVHkAdCnxMVpJd8YzHt/5pNXuSsdkDxNRkHfSC8REIZB88ASSXxrBdzU+tiVmBHs6UQMAiwgK6kCy4NsPACBPcfQ4VPtSSLdSfbjEmg0kVBkKMQ7lfIXKAoTnt8okPVbjwUqCJR//mtdMpaEa09ZwCZyopfk5g5Doye7UTz0JuUovfti0EIXvAbS7ebw3BaFIwaQAAwAqxzwAUTBYrE8AokG2AIpmHnZmzkG9aLHGcrE+1VoC14a3Nh90ZsKLOSTP/2OYc9pZxReq5Vce5UnqVfdJDlIXEklzOhkFpeXnJQQ7aN4tZcCsi0w0ehtrgMm/7xemAJ2n6/mNdtBseegXykCtJsrfx0ZC3RWA9hCO5uM0r4Ho8bjwgYwWIODoZYPZj8326UCpQPXFjsnAPEvtdA9k2XvegCV6ZZeq3av6fBvUvYZKsBbZiQI4tG0kQzW6kzrE1qfW/cWk+i1w14WuGxgvBOD+AP0MuCLRb/uuAAAAAElFTkSuQmCC"></head> <link rel="icon" type="image/x-icon" href="/favicon.ico">
</head>
<body style="background:black"> <body style="background:black">
<div id="buy_menu">
<h1>AI is expensive so you need to pay for it</h1>
<p>You can play for free 10 minutes per day</p>
<div id="daypass">
<button id="closebutton" onclick="closepayment()"><i class="fa-regular fa-xmark"></i></button>
<p>24 Hour Pass</p>
<button id="payment-button" onclick="do_checkout()">Buy Now</button>
</div>
<div id="codebox">
<label for="pass">Day pass code:</label>
<input type="password" name="pass" id="codeinput" onkeyup='on_codeinput_key(event);' required>
<button id="visibility-toggle" onclick="visibility_toggle()"><i class="fa fa-eye-slash" id="visibility-toggle-eye"></i></button>
</div>
</div>
<div id="inputdiv" class="inputdiv"> <div id="inputdiv" class="inputdiv">
<textarea onkeyup='on_textarea_key(event);' id="inputtext"> <textarea onkeyup='on_textarea_key(event);' id="inputtext">
</textarea> </textarea>
@ -109,7 +175,6 @@ body {
</div> </div>
<canvas class="game" id="canvas" oncontextmenu="event.preventDefault()" width="1504" height="864"></canvas> <canvas class="game" id="canvas" oncontextmenu="event.preventDefault()" width="1504" height="864"></canvas>
<script type="text/javascript"> <script type="text/javascript">
var Module = { var Module = {
preRun: [], preRun: [],
@ -137,10 +202,49 @@ body {
}; };
</script> </script>
<script type="text/javascript"> <script type="text/javascript">
let game_doing_input = true;
let server_url = "";
let my_code = "";
let code_visible = false;
let input_modal = document.getElementById("inputdiv");
let pause_modal = document.getElementById("buy_menu");
function visibility_toggle() {
if(code_visible) {
code_visible = false;
document.getElementById("codeinput").type = "password";
document.getElementById("visibility-toggle-eye").className = "fa fa-eye-slash";
} else {
code_visible = true;
document.getElementById("codeinput").type = "text";
document.getElementById("visibility-toggle-eye").className = "fa fa-eye";
}
}
function frame(delta) {
let input_visible = input_modal.style.display === "flex";
let pause_visible = pause_modal.style.display === "flex";
if( (input_visible || pause_visible) && game_doing_input)
{
Module.ccall('stop_controlling_input', 'void', [], []);
game_doing_input = false;
}
if( !input_visible && !pause_visible && !game_doing_input)
{
Module.ccall('start_controlling_input', 'void', [], []);
game_doing_input = true;
}
window.requestAnimationFrame(frame);
}
Module.onRuntimeInitialized = () => {
window.requestAnimationFrame(frame);
}
function start_dialog() { function start_dialog() {
document.getElementById("inputtext").value = ""; document.getElementById("inputtext").value = "";
setTimeout(function(){document.getElementById("inputtext").focus();},200); // for some reason focus doesn't work immediately here setTimeout(function(){document.getElementById("inputtext").focus();},50); // for some reason focus doesn't work immediately here
document.getElementById("inputdiv").style.display = "flex"; document.getElementById("inputdiv").style.display = "flex";
} }
function end_dialog() { function end_dialog() {
@ -148,6 +252,32 @@ function end_dialog() {
Module.ccall('end_text_input', 'void', ['string'], [document.getElementById("inputtext").value]); Module.ccall('end_text_input', 'void', ['string'], [document.getElementById("inputtext").value]);
} }
function show_paywall() {
document.getElementById('buy_menu').style.display = "flex";
}
function hide_paywall() {
document.getElementById('buy_menu').style.display = "none";
}
function on_codeinput_key(event) {
let final_codeinput_string = "";
let codeinput = document.getElementById("codeinput");
for(let i = 0; i < codeinput.value.length; i++) {
let cur = codeinput.value[i];
let cur_charcode = cur.charCodeAt(0);
if(cur_charcode >= "a".charCodeAt(0) && cur_charcode <= "z".charCodeAt(0)) {
final_codeinput_string += String.fromCharCode(cur_charcode + ("A".charCodeAt(0) - "a".charCodeAt(0)));
} else {
if((cur_charcode >= "A".charCodeAt(0) && cur_charcode <= "Z".charCodeAt(0)) || (cur_charcode >= "0".charCodeAt(0) && cur_charcode <= "9".charCodeAt(0))) {
final_codeinput_string += cur;
}
}
}
codeinput.value = final_codeinput_string;
my_code = codeinput.value;
}
function on_textarea_key(event) { function on_textarea_key(event) {
let final_textarea_string = ""; let final_textarea_string = "";
let cur_textarea_string = document.getElementById("inputtext").value; let cur_textarea_string = document.getElementById("inputtext").value;
@ -182,6 +312,20 @@ let generation_requests = []; // array of dictionaries with structure:
retries_remaining: number, // on failure, retries over and over until out of retries retries_remaining: number, // on failure, retries over and over until out of retries
} }
*/ */
function do_checkout() {
fetch(server_url + "/checkout").then((response) => response.text()).then((text) => {
split_up = text.split("|");
if(split_up.length !== 2) {
console.log("Weird response from server, length isn't 2");
}
my_code = split_up[0];
document.getElementById("codeinput").value = my_code;
let url_to_go_to = split_up[1];
window.location.href = url_to_go_to;
}).catch((error) => {
console.log("Error doing checkout: " + error);
});
}
function resend_request(r) { function resend_request(r) {
r.failed = false; r.failed = false;
@ -190,7 +334,12 @@ function resend_request(r) {
r.failed = true; r.failed = true;
}; };
r.request.open("POST", r.request_info.url, true); r.request.open("POST", r.request_info.url, true);
r.request.send(r.request_info.body); r.request.send(my_code + "|" + r.request_info.body);
}
// server URL is decided in C
function set_server_url(p) {
server_url = p;
} }
// Returns an integer, a "handle" into the generation request. Takes in the string prompt, and api URL // Returns an integer, a "handle" into the generation request. Takes in the string prompt, and api URL
@ -222,7 +371,19 @@ function get_generation_request_status(id) {
{ {
let http_status = generation_requests[i].request.status; let http_status = generation_requests[i].request.status;
if(http_status == 200) { if(http_status == 200) {
return 1; if(generation_requests[i].request.responseText[0] === "0")
{
show_paywall();
return 2;
}
else if(generation_requests[i].request.responseText[0] === "1")
{
return 1; // done, everything ok
}
else
{
console.log("Unknown body code " + generation_requests[i].request.responseText);
}
} }
else if(http_status == 0) { // not done yet else if(http_status == 0) { // not done yet
return 0; return 0;
@ -266,7 +427,7 @@ function get_generation_request_content(id) {
break; break;
} }
} }
return to_return;; return to_return.slice(1); // first character is a code that says if the request was successful or not
} }
</script> </script>
{{{ SCRIPT }}} {{{ SCRIPT }}}

Loading…
Cancel
Save