2023-02-03 03:48:40 +00:00
package main
import (
2023-02-10 19:47:09 +00:00
"bytes"
2023-02-03 03:48:40 +00:00
"fmt"
"io"
"os"
"strings"
2023-02-05 22:58:22 +00:00
"github.com/MakeNowJust/heredoc/v2"
"github.com/gdamore/tcell/v2"
2023-02-03 03:48:40 +00:00
"github.com/rivo/tview"
)
/ *
Assumes :
Match User join
ForceCommand / town / src / town / cmd / signup / signup
PubkeyAuthentication no
KbdInteractiveAuthentication no
PasswordAuthentication yes
PermitEmptyPasswords yes
DisableForwarding yes
in sshd_config , and :
auth [ success = done default = ignore ] pam_succeed_if . so user ingroup join
in / etc / pam . d / sshd
* /
2023-02-10 19:47:09 +00:00
const maxInputLength int = 4000
2023-02-03 05:03:03 +00:00
type TownApplication struct {
Email string
2023-02-03 03:48:40 +00:00
2023-02-03 05:03:03 +00:00
// TODO
}
2023-02-03 03:48:40 +00:00
type streams struct {
In io . Reader
Out io . Writer
Err io . Writer
}
2023-02-03 05:03:03 +00:00
func cli ( s * streams ) error {
pages := tview . NewPages ( )
mainFlex := tview . NewFlex ( )
innerFlex := tview . NewFlex ( )
input := tview . NewTextArea ( )
2023-02-10 00:22:04 +00:00
input . SetBorder ( true ) . SetBorderColor ( tcell . ColorPaleTurquoise )
input . SetTitle ( "press ctrl+d to send" )
2023-02-10 19:47:09 +00:00
input . SetMaxLength ( 2000 )
2023-02-03 05:03:03 +00:00
title := tview . NewTextView ( )
title . SetDynamicColors ( true )
title . SetTextAlign ( tview . AlignCenter )
title . SetText ( "[purple]the tilde town sign up portal[-]" )
msgScroll := tview . NewTextView ( )
msgScroll . SetDynamicColors ( true )
2023-02-10 00:22:04 +00:00
msgScroll . SetBackgroundColor ( tcell . ColorBlack )
msgScroll . SetTextColor ( tcell . ColorWhite )
2023-02-03 05:03:03 +00:00
sidebar := tview . NewTextView ( )
sidebar . SetBorder ( true )
sidebar . SetDynamicColors ( true )
2023-02-10 00:22:04 +00:00
sidebar . SetBackgroundColor ( tcell . ColorBlack )
sidebar . SetTextColor ( tcell . ColorGray )
2023-02-05 22:58:22 +00:00
sidebar . SetText ( heredoc . Doc ( `
[ - : - : b ] hey here are some hints [ - : - : - ]
quit by pressing [ - : - : b ] ctrl - c [ - : - : - ]
( your responses won ' t be saved )
type stuff . send it with [ - : - : b ] ctrl + d [ - : - : - ]
try a [ - : - : b ] verb [ - : - : - ] using [ - : - : b ] / [ - : - : - ] like :
2023-02-10 00:22:04 +00:00
[ - : - : b ] / nod [ - : - : - ]
2023-02-05 22:58:22 +00:00
` ) )
2023-02-03 05:03:03 +00:00
innerFlex . SetDirection ( tview . FlexColumn )
2023-02-10 00:22:04 +00:00
innerFlex . AddItem ( msgScroll , 0 , 3 , false )
2023-02-03 05:03:03 +00:00
innerFlex . AddItem ( sidebar , 0 , 1 , false )
mainFlex . SetDirection ( tview . FlexRow )
mainFlex . AddItem ( title , 1 , - 1 , false )
mainFlex . AddItem ( innerFlex , 0 , 1 , false )
mainFlex . AddItem ( input , 5 , - 1 , true )
pages . AddPage ( "main" , mainFlex , true , true )
app := tview . NewApplication ( )
app . SetRoot ( pages , true )
2023-02-05 22:58:22 +00:00
player := newCharacter ( "you" , "TODO" )
2023-02-03 03:48:40 +00:00
2023-02-10 19:47:09 +00:00
currentScene := "start"
2023-02-10 00:22:04 +00:00
sceneTransition := func ( text string ) {
fmt . Fprintln ( msgScroll , heredoc . Doc ( `
[ purple ] -- -- -- -- -- [ - : - : - ]
` ) )
fmt . Fprintln ( msgScroll , text )
}
2023-02-10 19:47:09 +00:00
type scene struct {
Description string
InBuff io . ReadWriter
InLength int
2023-02-10 20:05:06 +00:00
Host * character
2023-02-10 19:47:09 +00:00
}
scenes := map [ string ] * scene {
"start" : {
Description : heredoc . Doc ( `
2023-02-10 00:22:04 +00:00
You open your eyes .
You ' re in some kind of workshop .
Wires and computers in various state of disrepair are strewn across
tables and shelves . It smells faintly of burnt cedar .
The wires and components before you slowly drag
themselves into the shape of a small humanoid .
[ - : - : b ] wire guy says : [ - : - : - ]
hello , welcome to the application for membership in tilde town .
first , please let me know what a good [ - : - : b ] email address [ - : - : - ] is for you ?
just say it out loud . as many times as you need . to get it right .
when you ' re ready to move on , [ - : - : b ] / nod [ - : - : - ]
2023-02-10 19:47:09 +00:00
` ) ,
2023-02-10 20:05:06 +00:00
Host : newCharacter ( "wire guy" , "a lil homonculus made of discarded computer cables" ) ,
2023-02-10 19:47:09 +00:00
InBuff : bytes . NewBuffer ( [ ] byte { } ) ,
2023-02-10 00:22:04 +00:00
} ,
2023-02-10 19:47:09 +00:00
"nodded" : {
Description : heredoc . Doc ( `
2023-02-10 00:22:04 +00:00
The workshop fades away . You hear the sound of a dial up modem
in the distance .
Trees spring up out of the ground around you : birches , oaks , maples ,
firs , yews , pines , cypresses complete with tiny swamps around their trunks ,
junipers , redwoods , cedars , towering palms waving gently in a breeze , eucalyptus ,
banyan . the smell is riotous like a canvas with all the colors splashed on . birds
start to sing .
a shrike alights on a branch in front of you .
[ - : - : b ] the shrike says : [ - : - : - ]
phweeturpff . how did you find out about the town ? did anyone refer you ?
just say your answer out loud . when you ' ve said what you want , [ - : - : b ] / lean [ - : - : - ]
against a tree .
2023-02-10 19:47:09 +00:00
` ) ,
InBuff : bytes . NewBuffer ( [ ] byte { } ) ,
2023-02-10 20:05:06 +00:00
Host : newCharacter ( "the shrike" , "a little grey bird. it has a pretty song." ) ,
2023-02-10 00:22:04 +00:00
} ,
2023-02-10 19:47:09 +00:00
"leaned" : {
Description : heredoc . Doc ( `
2023-02-10 00:22:04 +00:00
You sink backwards into the forest . You find yourself floating in darkness .
At the far reaches of your vision you can make out a faint neon grid . Around you
float pieces of consumer electronic appliances from 1980 s Earth . A VCR approaches
you and speaks , flapping its tape slot cover with each word .
[ - : - : b ] the vcr says : [ - : - : - ]
welcome ! thank you for coming this far . just two questions left . what about
tilde town interests you ? what kind of stuff might you want to get up to here ?
as usual , just say your answer . when you ' re satisfied , please [ - : - : b ] / spin [ - : - : - ]
around in this void .
2023-02-10 19:47:09 +00:00
` ) ,
InBuff : bytes . NewBuffer ( [ ] byte { } ) ,
2023-02-10 20:05:06 +00:00
Host : newCharacter ( "the vcr" , "a black and grey VCR from 1991" ) ,
2023-02-10 00:22:04 +00:00
} ,
2023-02-10 19:47:09 +00:00
"spun" : {
Description : heredoc . Doc ( `
2023-02-10 00:22:04 +00:00
You realize your eyes have been shut . You open them and , in an instant ,
the neon grid and polygons are gone . You ' re in a convenience store . Outside
it ' s dark besides a single pool of light coming from a street lamp . it ' s illuminating
some litter and a rusty , blue 1994 pontiac grand am .
The shelves around you are stocked with products you ' ve never heard of before like
Visible Pants , Petty Burgers , Gentle Rice , Boo Sponge , Power Banjo , Superware , Kneephones ,
and Diet Coagulator . A mop is mopping the floor and turns to you .
[ - : - : b ] the mop says : [ - : - : - ]
swishy slop . last question . where online can we get to know you ? do you have a personal
website or social media presence ? we ' ll take whatever .
say some links and words out loud , you know the drill .
when you ' re happy you can submit this whole experience by leaving the
store . just [ - : - : b ] / open [ - : - : - ] the door .
2023-02-10 19:47:09 +00:00
` ) ,
InBuff : bytes . NewBuffer ( [ ] byte { } ) ,
2023-02-10 20:05:06 +00:00
Host : newCharacter ( "the mop" , "a greying mop with a wooden handle." ) ,
2023-02-10 00:22:04 +00:00
} ,
2023-02-10 19:47:09 +00:00
"done" : {
Description : heredoc . Doc ( `
2023-02-10 00:22:04 +00:00
thank you for applying to tilde . town !
please be on the look out for an email from [ - : - : b ] root @ tilde . town [ - : - : - ]
you can [ - : - : b ] / quit [ - : - : - ] now
ok bye have a good one ~
2023-02-10 19:47:09 +00:00
` ) ,
InBuff : bytes . NewBuffer ( [ ] byte { } ) ,
2023-02-10 00:22:04 +00:00
} ,
}
2023-02-05 22:58:22 +00:00
handleInput := func ( msg string ) {
2023-02-10 00:22:04 +00:00
trimmed := strings . TrimSpace ( msg )
if trimmed == "" {
return
}
if strings . HasPrefix ( trimmed , "/" ) {
split := strings . Split ( trimmed , " " )
if len ( split ) > 0 {
trimmed = split [ 0 ]
}
switch strings . TrimPrefix ( trimmed , "/" ) {
case "quit" :
2023-02-10 20:05:06 +00:00
app . Stop ( )
2023-02-10 19:47:09 +00:00
case "look" :
fmt . Fprintln ( msgScroll , "" )
fmt . Fprintln ( msgScroll , scenes [ currentScene ] . Description )
2023-02-10 00:22:04 +00:00
// TODO refactor into a state machine
case "nod" :
2023-02-10 19:47:09 +00:00
if currentScene != "start" {
2023-02-10 00:22:04 +00:00
return
}
2023-02-10 20:05:06 +00:00
if scenes [ currentScene ] . InLength == 0 {
fmt . Fprintln ( msgScroll ,
scenes [ currentScene ] . Host . Say ( "i'm sorry, before going further could you share an email with me?" ) )
return
2023-02-10 19:47:09 +00:00
}
2023-02-10 20:05:06 +00:00
currentScene = "nodded"
sceneTransition ( scenes [ currentScene ] . Description )
2023-02-10 00:22:04 +00:00
case "lean" :
2023-02-10 19:47:09 +00:00
if currentScene != "nodded" {
2023-02-10 00:22:04 +00:00
return
}
2023-02-10 20:05:06 +00:00
if scenes [ currentScene ] . InLength == 0 {
fmt . Fprintln ( msgScroll ,
scenes [ currentScene ] . Host . Say ( "phweeturpff" ) )
return
}
2023-02-10 19:47:09 +00:00
currentScene = "leaned"
sceneTransition ( scenes [ currentScene ] . Description )
2023-02-10 00:22:04 +00:00
case "spin" :
2023-02-10 19:47:09 +00:00
if currentScene != "leaned" {
2023-02-10 00:22:04 +00:00
return
}
2023-02-10 20:05:06 +00:00
if scenes [ currentScene ] . InLength == 0 {
fmt . Fprintln ( msgScroll ,
scenes [ currentScene ] . Host . Say ( "hmm did you say something?" ) )
return
}
2023-02-10 19:47:09 +00:00
currentScene = "spun"
sceneTransition ( scenes [ currentScene ] . Description )
2023-02-10 00:22:04 +00:00
case "open" :
2023-02-10 19:47:09 +00:00
if currentScene != "spun" {
2023-02-10 00:22:04 +00:00
return
}
2023-02-10 20:05:06 +00:00
if scenes [ currentScene ] . InLength == 0 {
fmt . Fprintln ( msgScroll ,
scenes [ currentScene ] . Host . Say ( "just the one last thing please" ) )
return
}
2023-02-10 19:47:09 +00:00
currentScene = "done"
sceneTransition ( scenes [ currentScene ] . Description )
2023-02-10 00:22:04 +00:00
}
return
2023-02-05 22:58:22 +00:00
}
fmt . Fprintln ( msgScroll , player . Say ( msg ) )
2023-02-10 19:47:09 +00:00
fmt . Fprintln ( scenes [ currentScene ] . InBuff , msg )
scenes [ currentScene ] . InLength += len ( msg )
2023-02-03 03:48:40 +00:00
}
2023-02-05 22:58:22 +00:00
app . SetInputCapture ( func ( event * tcell . EventKey ) * tcell . EventKey {
switch event . Key ( ) {
case tcell . KeyCtrlD :
handleInput ( input . GetText ( ) )
input . SetText ( "" , false )
return nil
2023-02-03 03:48:40 +00:00
}
2023-02-05 22:58:22 +00:00
return event
} )
2023-02-03 03:48:40 +00:00
2023-02-10 00:22:04 +00:00
app . SetAfterDrawFunc ( func ( _ tcell . Screen ) {
2023-02-10 19:47:09 +00:00
fmt . Fprintln ( msgScroll , scenes [ currentScene ] . Description )
2023-02-10 00:22:04 +00:00
app . SetAfterDrawFunc ( nil )
} )
2023-02-05 22:58:22 +00:00
return app . Run ( )
2023-02-03 03:48:40 +00:00
}
2023-02-10 00:22:04 +00:00
/ *
I need a script for how this interaction should go . It should feel more organic and fluid than just filling out a form . It shouldn ' t , however , take more than 10 minutes to get through stuff . The end result is to collect :
- an email address
- how a user found out about the town
- if they have a referral from an existing member
- what interests them about the town
- any links to personal websites or social media
A given is that the applicant is interacting with a "host" that guides them through the process . Should there be more than one host ?
How many rooms should there be ? 1 ? more ?
in terms of data collection , I intend to just save the transcript of their interaction instead of more structured data . The only real structured data are email and referral .
another idea / thought / dream :
the whole point of this is to spatialize a form . it ' s a form ! a set of questions and corresponding textarea elements . but in order to trigger self reflection -- as well as a feeling of being seen by another ( in a good way ) -- i want to turn the form into a shape . i ' m thinking about this like the room in Eclipse Penumbra :
( For the Hollow Head was drug paraphenalia you could walk into . The building itself was the syringe , or the hookah , or the sniff - tube )
so , spatialized , every room is a question . the rooms take shape as a linear script the user moves through by :
1. answering a question
2. executing a verb
and finally at the end , a verb to confirm submission .
one idea but i ' m scratchin :
opening :
It ' s dark . Before you a button appears . It ' s a pink circle with a ~ at the center . It looks like a button . You could press it .
/ press button
A thin seam splits the darkness from the top and buttom of the button . The darkness splits apart and you ' re standing in a forest clearing . Birds are singing in strange octaves .
A cube is here , floating .
cube : hello ! how are you ? you can respond to me by typing anything and pressing "ctrl+d"
< input >
cube : thank you for telling me . i ' m just a program so i can ' t really understand . i hope you forgive me .
cube : i ' m here to help you apply for a homestead over there in tilde town .
the cube inclines in the direction of what looks like a far off town .
cube : first , what is an email address we can use to contact you ?
< input >
cube : cool , thank you . that ' s < email > right ? if so , you can / nod to confirm . otherwise just tell me again what your email is .
< nod >
cube : excellent . next , you ' ll talk to
* /
2023-02-05 22:58:22 +00:00
type character struct {
Name string
Description string
2023-02-03 03:48:40 +00:00
}
2023-02-05 22:58:22 +00:00
func newCharacter ( name , description string ) * character {
return & character {
Name : name ,
Description : description ,
2023-02-03 03:48:40 +00:00
}
}
2023-02-05 22:58:22 +00:00
func ( c * character ) Say ( msg string ) string {
verb := "says"
if c . Name == "you" {
verb = "say"
2023-02-03 03:48:40 +00:00
}
2023-02-05 22:58:22 +00:00
return fmt . Sprintf ( "[-:-:b]%s[-:-:-] %s: '%s'" ,
c . Name ,
verb ,
strings . TrimSpace ( msg ) )
2023-02-03 03:48:40 +00:00
}
2023-02-05 22:58:22 +00:00
func main ( ) {
s := & streams {
In : os . Stdin ,
Out : os . Stdout ,
Err : os . Stderr ,
2023-02-03 03:48:40 +00:00
}
2023-02-05 22:58:22 +00:00
err := cli ( s )
if err != nil {
fmt . Fprintln ( os . Stderr , err )
os . Exit ( 1 )
2023-02-03 03:48:40 +00:00
}
}
2023-02-10 00:22:04 +00:00
/ *
From the start point the player advances the "form" by performing a tactile action . for now it ' s a button press but it could be something more interesting later . Keeping this as a one room , limited - verb thing is good for accessibility .
so the script is a list of strings that advance by tactility ... is it dialogue ? or scene description ? i think i should fake dialogue / as / scene description .
You open your eyes . You ' re standing in a small room . It ' s dim , lit softly by a mix of candles and electro - luminescent wire . Computers .
* /
2023-02-05 22:58:22 +00:00
/ *
2023-02-03 03:48:40 +00:00
type answers struct {
username string
email string
applied time . Time
howDay string
howHeard string
reasons string
plans string
socials string
sshKey string
}
func _main ( args [ ] string , s * streams ) error {
p . Say ( "you are standing in a field." )
p . Pause ( )
p . Say ( "there are flowers around you. you are standing in a slight depression and before you is grass touching a purple sky." )
p . Pause ( )
p . Say ( "you are not sure how long it's been when a cube about a meter high appears at the top of the hill before you." )
p . Say ( "the cube's surface is murky but iridescently reflective like an oil slick." )
p . Pause ( )
p . CharSay ( "cube" , "hello. how is your day going?" )
p . Say ( "(you can type a response and hit enter to talk to the cube)" )
p . Pause ( )
howDay := InputAnswer { }
err := p . Ask ( "you say:" , & howDay )
if err != nil {
return err
}
a . howDay = howDay . Value
p . Say ( "the cube inclines towards you gently as if nodding." )
p . CharSay ( "cube" , "i see." )
p . Pause ( )
p . CharSay ( "cube" , "i am guessing that if you are here, you want to be there." )
p . Pause ( )
p . Say ( "you blink and are somewhere else." )
p . Pause ( )
p . Say ( "the field of flowers is behind you and now you are up on the hill. the cube is next to you." )
p . Pause ( )
p . Say ( "you can see clear across a wide open plain. structures large and small dot the landscape. you catch a whiff of a savory smell and can just barely hear voices on the wind." )
p . Pause ( )
p . CharSay ( "cube" , "the tilde town lies before us. if you want to continue, i'll ask you some questions about acquiring a home down in the town. you'll be free to edit your responses before i carry them off." )
p . Pause ( )
p . CharSay ( "cube" , "first, i'm curious how you found out about the town?" )
p . Pause ( )
answer := InputAnswer { }
if err := p . Ask ( "you say:" , & answer ) ; err != nil {
return err
}
a . howHeard = answer . Value
p . CharSay ( "cube" , "interesting, thanks." )
p . CharSay ( "cube" , "what sort of stuff do you want to get up to on the town?" )
if err := p . Ask ( "you say:" , & answer ) ; err != nil {
return err
}
a . plans = answer . Value
p . CharSay ( "cube" , "thanks." )
p . CharSay ( "cube" , "what do you like about the town?" )
if err := p . Ask ( "you say:" , & answer ) ; err != nil {
return err
}
a . reasons = answer . Value
p . CharSay ( "cube" , "i appreciate it." )
p . CharSay ( "cube" , "can you paste some links to other places you are active online? maybe a homepage or social media profile? if you aren't comfortable sharing or there are none, just say so." )
if err := p . Ask ( "you say:" , & answer ) ; err != nil {
return err
}
a . socials = answer . Value
p . CharSay ( "cube" , "ok, last thing. in order to break ground in the town, you'll need an SSH key. If you don't know what that is, you can check out this link: https://tilde.town/ssh.html ." )
p . Pause ( )
if err := p . AskLong ( "paste SSH public key; press control+d to submit:" , & answer ) ; err != nil {
return err
}
a . sshKey = answer . Value
p . CharSay ( "cube" , "i know that was a lot so i appreciate it. i've got everything written down here. before i carry it off, do you want to review and edit what you wrote?" )
}
2023-02-05 22:58:22 +00:00
* /