399 lines
8.6 KiB
Go
399 lines
8.6 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"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
|
|
*/
|
|
|
|
type TownApplication struct {
|
|
Email string
|
|
|
|
// TODO
|
|
}
|
|
|
|
type streams struct {
|
|
In io.Reader
|
|
Out io.Writer
|
|
Err io.Writer
|
|
}
|
|
|
|
func cli(s *streams) error {
|
|
pages := tview.NewPages()
|
|
mainFlex := tview.NewFlex()
|
|
innerFlex := tview.NewFlex()
|
|
input := tview.NewTextArea()
|
|
|
|
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)
|
|
|
|
sidebar := tview.NewTextView()
|
|
sidebar.SetBorder(true)
|
|
sidebar.SetDynamicColors(true)
|
|
sidebar.SetText("[-:-:b]pbbt[-:-:-]")
|
|
|
|
innerFlex.SetDirection(tview.FlexColumn)
|
|
innerFlex.AddItem(msgScroll, 0, 2, false)
|
|
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)
|
|
|
|
return app.Run()
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
type Prompter struct {
|
|
in io.Reader
|
|
out io.Writer
|
|
width int
|
|
}
|
|
|
|
func (p *Prompter) Say(m string) {
|
|
for _, line := range wrap(m, p.width) {
|
|
fmt.Fprintln(p.out, line)
|
|
}
|
|
}
|
|
|
|
func (p *Prompter) CharSay(c, m string) {
|
|
sayPrefix := fmt.Sprintf("%s says: ", c)
|
|
prefixWidth := utf8.RuneCountInString(sayPrefix)
|
|
width := p.width - prefixWidth
|
|
indent := ""
|
|
for x := 0; x < utf8.RuneCountInString(sayPrefix); x++ {
|
|
indent += " "
|
|
}
|
|
for i, line := range wrap(m, width) {
|
|
if i == 0 {
|
|
fmt.Fprintln(p.out, sayPrefix+line)
|
|
} else {
|
|
fmt.Fprintln(p.out, indent+line)
|
|
}
|
|
}
|
|
}
|
|
|
|
type InputAnswer struct {
|
|
Value string
|
|
}
|
|
|
|
func runeLen(s string) int {
|
|
return utf8.RuneCountInString(s)
|
|
}
|
|
|
|
func wrap(s string, width int) []string {
|
|
fielded := strings.Fields(s)
|
|
out := []string{}
|
|
line := ""
|
|
for i, field := range fielded {
|
|
if runeLen(field)+runeLen(line)+1 < width {
|
|
line += field + " "
|
|
} else {
|
|
out = append(out, line)
|
|
line = field + " "
|
|
continue
|
|
}
|
|
if i == len(fielded)-1 {
|
|
out = append(out, line)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (p *Prompter) Confirm(m string) (bool, error) {
|
|
result := InputAnswer{}
|
|
|
|
for {
|
|
err := p.Ask(m, &result)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
val := strings.TrimSpace(result.Value)
|
|
|
|
switch val {
|
|
case "yes", "y":
|
|
return true, nil
|
|
case "no", "n":
|
|
return false, nil
|
|
default:
|
|
p.Say("sorry, please say y or n")
|
|
p.Pause()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *Prompter) Ask(m string, result *InputAnswer) error {
|
|
fmt.Fprintf(p.out, "%s ", m)
|
|
var val string
|
|
var err error
|
|
|
|
reader := bufio.NewReader(p.in)
|
|
for val == "" {
|
|
val, err = reader.ReadString('\n')
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if val == "" {
|
|
p.Say("hmm, what was that?")
|
|
p.Pause()
|
|
}
|
|
}
|
|
|
|
result.Value = val
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Prompter) AskLong(m string, result *InputAnswer) error {
|
|
fmt.Fprintf(p.out, "%s\n", m)
|
|
var val []byte
|
|
var err error
|
|
|
|
reader := bufio.NewReader(p.in)
|
|
for len(val) == 0 {
|
|
val, err = ioutil.ReadAll(reader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(val) == 0 {
|
|
fmt.Fprintf(p.out, "%s\n", m)
|
|
}
|
|
}
|
|
|
|
result.Value = string(val)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Prompter) Pause() {
|
|
fmt.Fprintln(p.out)
|
|
time.Sleep(1 * time.Second)
|
|
}
|
|
|
|
func NewPrompter(width int, s *streams) Prompter {
|
|
return Prompter{
|
|
in: s.In,
|
|
out: s.Out,
|
|
width: width,
|
|
}
|
|
}
|
|
|
|
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 {
|
|
a := answers{}
|
|
|
|
p := NewPrompter(80, s)
|
|
|
|
// disable input buffering
|
|
exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run()
|
|
|
|
// LOL i don't think this will work
|
|
//reader := bufio.NewReader(p.in)
|
|
//s, _ := reader.ReadString(4)
|
|
fmt.Printf("DBG %#v\n", s)
|
|
//var b []byte = make([]byte, 100)
|
|
//for {
|
|
// os.Stdin.Read(b)
|
|
// fmt.Println("I got the byte", b, "("+string(b)+")")
|
|
//}
|
|
|
|
var ia InputAnswer
|
|
if err := p.AskLong("lol give me stuff hit ctrl+d", &ia); err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("DBG %#v\n", ia.Value)
|
|
|
|
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?")
|
|
|
|
confirmed, err := p.Confirm("type y or n: ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if confirmed {
|
|
err := edit(s, &a)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
fmt.Printf("DBG %#v\n", confirmed)
|
|
|
|
fmt.Printf("DBG %#v\n", a)
|
|
|
|
// TODO allow for editing
|
|
// TODO write answers to disk
|
|
// TODO add a log
|
|
// TODO pretty colors
|
|
// TODO ascii art
|
|
// TODO IP throttling?
|
|
|
|
return nil
|
|
}
|
|
|
|
func edit(s *streams, a *answers) error {
|
|
// TODO make more real
|
|
/*
|
|
username string
|
|
email string
|
|
applied time.Time
|
|
|
|
howDay string
|
|
howHeard string
|
|
reasons string
|
|
plans string
|
|
socials string
|
|
sshKey string
|
|
*/
|
|
// TODO add note about tabbing around
|
|
|
|
app := tview.NewApplication()
|
|
form := tview.NewForm().
|
|
AddInputField("how did you hear about the town?", a.howHeard, 0, nil, nil).
|
|
AddButton("cool i'm good", nil).
|
|
AddButton("cancel and discard all this please", func() { app.Stop() })
|
|
form.SetBorder(true).SetTitle("edit your stuff").SetTitleAlign(tview.AlignCenter)
|
|
|
|
return app.SetRoot(form, true).EnableMouse(true).Run()
|
|
}
|