forked from tildetown/town
save answers to file, some cleanup (broken)
parent
4a881d3b8b
commit
462e8772ec
|
@ -1,61 +1,79 @@
|
||||||
The point of this project is to enable signing up for tilde.town via an ssh connection.
|
# town signup
|
||||||
|
The point of this command is to enable signing up for tilde.town via an ssh connection. It is designed to be run when `join@tilde.town` is SSH'd to.
|
||||||
|
|
||||||
It ought to work something like this:
|
## to-dos
|
||||||
|
|
||||||
```bash
|
- [ ] finish this command
|
||||||
|
- [x] interactive guts
|
||||||
ssh signup@tilde.town
|
- [x] logging
|
||||||
|
- [ ] write answers to disk
|
||||||
<ascii art>
|
- [ ] splash screen - put off
|
||||||
|
- [ ] easter egg commands - put off
|
||||||
tilde.town
|
- [ ] inactivity timer(?) - put off
|
||||||
|
- [ ] review tool
|
||||||
|
- [ ] iterate over answers
|
||||||
|
- [ ] accept
|
||||||
|
- [ ] notate
|
||||||
|
- [ ] reject
|
||||||
|
- [ ] send email with directions on key upload
|
||||||
|
- [ ] actual account creation
|
||||||
|
- [ ] accept key
|
||||||
|
- [ ] accept username
|
||||||
|
- [ ] create user
|
||||||
|
- [ ] backlog
|
||||||
|
- [ ] get a manual dump from psql of json
|
||||||
|
- [ ] convert into files in the review directory
|
||||||
|
|
||||||
|
|
||||||
a creature stands before you. what does it look like?
|
## configuration
|
||||||
|
|
||||||
> a floating gray cube
|
It assumes, in `sshd_config`:
|
||||||
a calico cat with softly glowing eyes
|
|
||||||
a squid
|
|
||||||
something else
|
|
||||||
|
|
||||||
cube: you'd like to sign up for tilde.town, yes?
|
|
||||||
|
|
||||||
> yeah
|
|
||||||
nah
|
|
||||||
why should I?
|
|
||||||
|
|
||||||
you say, "yeah"
|
|
||||||
|
|
||||||
cube: by what name should i call you?
|
|
||||||
|
|
||||||
~cowcow________
|
|
||||||
|
|
||||||
cube: unfortunately, i already know someone by that name. what else can i call you?
|
|
||||||
|
|
||||||
~shelf_________
|
|
||||||
|
|
||||||
cube: excellent, hello ~shelf. have you been invited here by anyone i know?
|
|
||||||
|
|
||||||
> yes, i have an invite code
|
|
||||||
no
|
|
||||||
|
|
||||||
you say "yes, i have an invite code"
|
|
||||||
|
|
||||||
cube: great. please paste it and press enter:
|
|
||||||
|
|
||||||
_____________
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
Match User join
|
||||||
|
ForceCommand /town/src/town/cmd/signup/signup
|
||||||
|
PubkeyAuthentication no
|
||||||
|
KbdInteractiveAuthentication no
|
||||||
|
PasswordAuthentication yes
|
||||||
|
PermitEmptyPasswords yes
|
||||||
|
DisableForwarding yes
|
||||||
|
```
|
||||||
|
|
||||||
...and so on.
|
and in `/etc/pam.d/sshd`:
|
||||||
|
|
||||||
TODO
|
```
|
||||||
|
auth [success=done default=ignore] pam_succeed_if.so user ingroup join
|
||||||
|
```
|
||||||
|
|
||||||
- [ ] make signup user
|
## initial thoughts
|
||||||
- [ ] get dummy program to run as ssh handler
|
|
||||||
- [ ] tcell bootstrapping some kind of interactivity
|
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:
|
||||||
- [ ] existing username check
|
|
||||||
- [ ] email validation
|
- an email address
|
||||||
- [ ] collect responses
|
- how a user found out about the town
|
||||||
- [ ] create the signup request (on disk? db?)
|
- if they have a referral from an existing member
|
||||||
- [ ] invite system support
|
- 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.
|
||||||
|
|
||||||
|
## author
|
||||||
|
|
||||||
|
vilmibm
|
||||||
|
|
|
@ -4,48 +4,80 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/MakeNowJust/heredoc/v2"
|
"github.com/MakeNowJust/heredoc/v2"
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/rivo/tview"
|
"github.com/rivo/tview"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
const (
|
||||||
Assumes:
|
maxInputLength int = 10000
|
||||||
|
signupDirectory string = "/town/signups"
|
||||||
|
logDir string = "/town/var/signup"
|
||||||
|
)
|
||||||
|
|
||||||
Match User join
|
type scene struct {
|
||||||
ForceCommand /town/src/town/cmd/signup/signup
|
Name string
|
||||||
PubkeyAuthentication no
|
Description string
|
||||||
KbdInteractiveAuthentication no
|
InBuff io.ReadWriter
|
||||||
PasswordAuthentication yes
|
InLength int
|
||||||
PermitEmptyPasswords yes
|
Host *character
|
||||||
DisableForwarding yes
|
|
||||||
|
|
||||||
in sshd_config, and:
|
|
||||||
|
|
||||||
auth [success=done default=ignore] pam_succeed_if.so user ingroup join
|
|
||||||
|
|
||||||
in /etc/pam.d/sshd
|
|
||||||
*/
|
|
||||||
|
|
||||||
const maxInputLength int = 4000
|
|
||||||
|
|
||||||
type TownApplication struct {
|
|
||||||
Email string
|
|
||||||
HowFound string
|
|
||||||
Why string
|
|
||||||
Where string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type streams struct {
|
type townApplication struct {
|
||||||
In io.Reader
|
Email io.ReadWriter
|
||||||
Out io.Writer
|
How io.ReadWriter
|
||||||
Err io.Writer
|
Why io.ReadWriter
|
||||||
|
Where io.ReadWriter
|
||||||
|
Extra io.ReadWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
func cli(s *streams) error {
|
type character struct {
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCharacter(name, description string) *character {
|
||||||
|
return &character{
|
||||||
|
Name: name,
|
||||||
|
Description: description,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *character) Say(msg string) string {
|
||||||
|
verb := "says"
|
||||||
|
if c.Name == "you" {
|
||||||
|
verb = "say"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("[-:-:b]%s[-:-:-] %s: '%s'",
|
||||||
|
c.Name,
|
||||||
|
verb,
|
||||||
|
strings.TrimSpace(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
logFile := path.Join(logDir, fmt.Sprintf("%d", time.Now().Unix()))
|
||||||
|
logF, err := os.Create(logFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := log.New(logF, "", log.Ldate|log.Ltime)
|
||||||
|
err = cli(logger)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cli(l *log.Logger) error {
|
||||||
pages := tview.NewPages()
|
pages := tview.NewPages()
|
||||||
mainFlex := tview.NewFlex()
|
mainFlex := tview.NewFlex()
|
||||||
innerFlex := tview.NewFlex()
|
innerFlex := tview.NewFlex()
|
||||||
|
@ -98,12 +130,56 @@ func cli(s *streams) error {
|
||||||
|
|
||||||
player := newCharacter("you", "TODO")
|
player := newCharacter("you", "TODO")
|
||||||
|
|
||||||
type scene struct {
|
answers := &townApplication{
|
||||||
Name string
|
Email: bytes.NewBuffer([]byte{}),
|
||||||
Description string
|
How: bytes.NewBuffer([]byte{}),
|
||||||
InBuff io.ReadWriter
|
Why: bytes.NewBuffer([]byte{}),
|
||||||
InLength int
|
Where: bytes.NewBuffer([]byte{}),
|
||||||
Host *character
|
Extra: bytes.NewBuffer([]byte{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
save := func() {
|
||||||
|
when := time.Now()
|
||||||
|
// TODO this code sucks
|
||||||
|
email := []byte{}
|
||||||
|
how := []byte{}
|
||||||
|
why := []byte{}
|
||||||
|
where := []byte{}
|
||||||
|
extra := []byte{}
|
||||||
|
_, _ = answers.Email.Read(email)
|
||||||
|
_, _ = answers.Why.Read(why)
|
||||||
|
_, _ = answers.How.Read(how)
|
||||||
|
_, _ = answers.Where.Read(where)
|
||||||
|
_, _ = answers.Extra.Read(extra)
|
||||||
|
d := map[string]interface{}{
|
||||||
|
"when": when,
|
||||||
|
"email": email,
|
||||||
|
"how": how,
|
||||||
|
"why": why,
|
||||||
|
"where": where,
|
||||||
|
"extra": extra,
|
||||||
|
}
|
||||||
|
output, err := yaml.Marshal(d)
|
||||||
|
if err != nil {
|
||||||
|
l.Printf("could not serialize stuff: %s", err.Error())
|
||||||
|
l.Printf("dumping values: %v", d)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create(path.Join(signupDirectory, fmt.Sprintf("%d", when.Unix())))
|
||||||
|
if err != nil {
|
||||||
|
l.Printf("could not open signup file: %s", err.Error())
|
||||||
|
l.Printf("dumping values: %s", string(output))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
_, err = f.Write(output)
|
||||||
|
if err != nil {
|
||||||
|
l.Printf("failed to write to file: %s", err.Error())
|
||||||
|
l.Printf("dumping values: %s", string(output))
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scenes := []*scene{
|
scenes := []*scene{
|
||||||
|
@ -126,7 +202,7 @@ func cli(s *streams) error {
|
||||||
when you're ready to move on, [-:-:b]/nod[-:-:-]
|
when you're ready to move on, [-:-:b]/nod[-:-:-]
|
||||||
`),
|
`),
|
||||||
Host: newCharacter("wire guy", "a lil homonculus made of discarded computer cables"),
|
Host: newCharacter("wire guy", "a lil homonculus made of discarded computer cables"),
|
||||||
InBuff: bytes.NewBuffer([]byte{}),
|
InBuff: answers.Email,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "nodded",
|
Name: "nodded",
|
||||||
|
@ -148,7 +224,7 @@ func cli(s *streams) error {
|
||||||
just say your answer out loud. when you've said what you want, [-:-:b]/lean[-:-:-]
|
just say your answer out loud. when you've said what you want, [-:-:b]/lean[-:-:-]
|
||||||
against a tree.
|
against a tree.
|
||||||
`),
|
`),
|
||||||
InBuff: bytes.NewBuffer([]byte{}),
|
InBuff: answers.How,
|
||||||
Host: newCharacter("the shrike", "a little grey bird. it has a pretty song."),
|
Host: newCharacter("the shrike", "a little grey bird. it has a pretty song."),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -167,7 +243,7 @@ func cli(s *streams) error {
|
||||||
as usual, just say your answer. when you're satisfied, please [-:-:b]/spin[-:-:-]
|
as usual, just say your answer. when you're satisfied, please [-:-:b]/spin[-:-:-]
|
||||||
around in this void.
|
around in this void.
|
||||||
`),
|
`),
|
||||||
InBuff: bytes.NewBuffer([]byte{}),
|
InBuff: answers.Why,
|
||||||
Host: newCharacter("the vcr", "a black and grey VCR from 1991"),
|
Host: newCharacter("the vcr", "a black and grey VCR from 1991"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -191,11 +267,11 @@ func cli(s *streams) error {
|
||||||
when you're happy you can submit this whole experience by leaving the
|
when you're happy you can submit this whole experience by leaving the
|
||||||
store. just [-:-:b]/open[-:-:-] the door.
|
store. just [-:-:b]/open[-:-:-] the door.
|
||||||
`),
|
`),
|
||||||
InBuff: bytes.NewBuffer([]byte{}),
|
InBuff: answers.Where,
|
||||||
Host: newCharacter("the mop", "a greying mop with a wooden handle."),
|
Host: newCharacter("the mop", "a greying mop with a wooden handle."),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "spun",
|
Name: "done",
|
||||||
Description: heredoc.Doc(`
|
Description: heredoc.Doc(`
|
||||||
thank you for applying to tilde.town!
|
thank you for applying to tilde.town!
|
||||||
|
|
||||||
|
@ -205,7 +281,7 @@ func cli(s *streams) error {
|
||||||
|
|
||||||
ok bye have a good one~
|
ok bye have a good one~
|
||||||
`),
|
`),
|
||||||
InBuff: bytes.NewBuffer([]byte{}),
|
InBuff: answers.Extra,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,6 +296,7 @@ func cli(s *streams) error {
|
||||||
fmt.Fprintln(msgScroll, currentScene.Host.Say(sorryMsg))
|
fmt.Fprintln(msgScroll, currentScene.Host.Say(sorryMsg))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// TODO put contents of InBuff into answers struct
|
||||||
sceneIx++
|
sceneIx++
|
||||||
currentScene = scenes[sceneIx]
|
currentScene = scenes[sceneIx]
|
||||||
fmt.Fprintln(msgScroll, heredoc.Doc(`
|
fmt.Fprintln(msgScroll, heredoc.Doc(`
|
||||||
|
@ -256,9 +333,15 @@ func cli(s *streams) error {
|
||||||
advanceScene("leaned", "hmm did you say something?")
|
advanceScene("leaned", "hmm did you say something?")
|
||||||
case "open":
|
case "open":
|
||||||
advanceScene("spun", "just the one last thing please")
|
advanceScene("spun", "just the one last thing please")
|
||||||
|
save()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if currentScene.InLength+len(msg) > maxInputLength {
|
||||||
|
fmt.Fprintln(msgScroll,
|
||||||
|
currentScene.Host.Say("sorry I've heard more than I can remember :( maybe it's time to move on"))
|
||||||
|
return
|
||||||
|
}
|
||||||
fmt.Fprintln(msgScroll, player.Say(msg))
|
fmt.Fprintln(msgScroll, player.Say(msg))
|
||||||
fmt.Fprintln(currentScene.InBuff, msg)
|
fmt.Fprintln(currentScene.InBuff, msg)
|
||||||
currentScene.InLength += len(msg)
|
currentScene.InLength += len(msg)
|
||||||
|
@ -282,70 +365,3 @@ func cli(s *streams) error {
|
||||||
|
|
||||||
return app.Run()
|
return app.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
type character struct {
|
|
||||||
Name string
|
|
||||||
Description string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newCharacter(name, description string) *character {
|
|
||||||
return &character{
|
|
||||||
Name: name,
|
|
||||||
Description: description,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *character) Say(msg string) string {
|
|
||||||
verb := "says"
|
|
||||||
if c.Name == "you" {
|
|
||||||
verb = "say"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("[-:-:b]%s[-:-:-] %s: '%s'",
|
|
||||||
c.Name,
|
|
||||||
verb,
|
|
||||||
strings.TrimSpace(msg))
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
s := &streams{
|
|
||||||
In: os.Stdin,
|
|
||||||
Out: os.Stdout,
|
|
||||||
Err: os.Stderr,
|
|
||||||
}
|
|
||||||
err := cli(s)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# New commands for signups
|
# New commands for signups, rough draft
|
||||||
|
|
||||||
## town-signup
|
## town-signup
|
||||||
|
|
||||||
|
@ -39,8 +39,3 @@ this binary helps manage keys for users; basically automating the listing, addin
|
||||||
- `user-key add <username> <keyfile`
|
- `user-key add <username> <keyfile`
|
||||||
- `user-key list <username>`
|
- `user-key list <username>`
|
||||||
- `user-key remove <username>`
|
- `user-key remove <username>`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue