package main import ( "embed" "fmt" "math/rand" "os" "os/exec" "os/user" "path" "strings" "git.tilde.town/tildetown/town/email" "github.com/charmbracelet/glamour" "github.com/charmbracelet/huh" "gopkg.in/yaml.v3" ) //go:embed banners/* var banners embed.FS //go:embed md/* var md embed.FS func banner() (string, error) { dirs, err := banners.ReadDir("banners") if err != nil { return "", err } mx := len(dirs) ix := rand.Intn(mx) content, err := banners.ReadFile(path.Join("banners", dirs[ix].Name())) if err != nil { return "", err } return string(content), nil } func info() error { infoContent, err := md.ReadFile("md/info.md") if err != nil { return err } out, err := glamour.Render(string(infoContent), "auto") if err != nil { return err } cmd := exec.Command("/usr/bin/bat") cmd.Stdin = strings.NewReader(out) cmd.Stdout = os.Stdout return cmd.Run() } func schedule() error { scheduleContent, err := md.ReadFile("md/schedule.md") if err != nil { return err } r, _ := glamour.NewTermRenderer( // detect background color and pick either the default dark or light theme glamour.WithAutoStyle(), // wrap output at specific width (default is 80) glamour.WithWordWrap(120), ) out, err := r.Render(string(scheduleContent)) if err != nil { return err } cmd := exec.Command("/usr/bin/bat") cmd.Stdin = strings.NewReader(out) cmd.Stdout = os.Stdout return cmd.Run() } type physicalData struct { Arriving string Departing string Transportation bool Lodging bool Allergies string Couch bool } type rsvpData struct { Username string Being string PhysicalData *physicalData `yaml:"PhysicalData,omitempty"` Freeform string } func sendRSVPEmail(rd rsvpData) error { bs, err := yaml.Marshal(rd) if err != nil { return err } return email.SendLocalEmail("vilmibm", "RSVP TOWNCON24", string(bs)) } func rsvp(o opts) error { var being string var arriving string var departing string var transportation bool lodging := true var allergies string couch := true var freeform string form := huh.NewForm( huh.NewGroup( huh.NewSelect[string]().Title("How will you be attending?"). Options( huh.NewOption("Digitally", "digital"), huh.NewOption("Phyiscally", "physical"), ).Value(&being)), // Physical attendee form huh.NewGroup( huh.NewInput().Title("When (day/time) are you arriving?"). Value(&arriving), huh.NewInput().Title("When (day/time) are you departing?"). Value(&departing), ).WithHideFunc(func() bool { return being != "physical" }), huh.NewGroup( huh.NewConfirm().Title("Will you be staying overnight at the venue?"). Value(&lodging), ).WithHideFunc(func() bool { return being != "physical" }), huh.NewGroup( huh.NewConfirm().Title("If the need arises are you ok sleeping on a couch?"). Value(&couch), ).WithHideFunc(func() bool { return being != "physical" || !lodging }), huh.NewGroup( huh.NewInput().Title("Do you have any food allergies I should be aware of?"). Value(&allergies), huh.NewConfirm().Title("Will you need any help getting to the venue?"). Description("I have a car and have some ability to help getting people to the venue"). Value(&transportation), ).WithHideFunc(func() bool { return being != "physical" }), // Catch all freeform huh.NewGroup( huh.NewText(). Title("Anything you want me to know? Any questions?"). Value(&freeform))).WithTheme(huh.ThemeBase()) err := form.Run() if err != nil { return err } var pd *physicalData if being == "physical" { pd = &physicalData{ Arriving: arriving, Departing: departing, Transportation: transportation, Lodging: lodging, Allergies: allergies, Couch: couch, } } rd := rsvpData{ Username: o.Username, Being: being, Freeform: freeform, PhysicalData: pd} if err = sendRSVPEmail(rd); err != nil { return err } fmt.Println("THANK YOUUUU!U!U!UU!U!!!!~~~!~!~~!!!!!UUU") return nil } type submitData struct { Username string Live bool When string Desc string Length string Interactive bool Freeform string } func sendSubmitEmail(sd submitData) error { bs, err := yaml.Marshal(sd) if err != nil { return err } return email.SendLocalEmail("vilmibm", "SUBMISSION TOWNCON24", string(bs)) } func submit(o opts) error { submitContent, err := md.ReadFile("md/submit.md") if err != nil { return err } out, err := glamour.Render(string(submitContent), "auto") if err != nil { return err } fmt.Println(out) submitting := true var live bool var talkWhen string var length string var desc string var freeform string var interactive bool notSubmitting := func() bool { return !submitting } form := huh.NewForm( huh.NewGroup( huh.NewConfirm().Title("So...wanna submit a thing?").Value(&submitting), ), huh.NewGroup( huh.NewConfirm().Title("Excellent. Is it a live talk?").Value(&live), ).WithHideFunc(notSubmitting), huh.NewGroup( huh.NewInput().Title("During what time frame can you give the talk"). Description("please include relevant time zone information!").Value(&talkWhen), ).WithHideFunc(func() bool { return !live || !submitting }), huh.NewGroup( huh.NewText().Title("Please describe your submission"). Description("Include details like media format, topic, whether it's text or browser based.").Value(&desc), ).WithHideFunc(notSubmitting), huh.NewGroup( huh.NewInput().TitleFunc(func() string { if live { return "About how long is your talk?" } else { return "If your piece has a duration (eg audio/video), approximately what is it?" } }, &live).Value(&length), ).WithHideFunc(notSubmitting), huh.NewGroup( huh.NewConfirm().Title("Is your submission interactive?"). Description("for example a game or website"). Value(&interactive), ).WithHideFunc(func() bool { return !submitting || live }), huh.NewGroup( huh.NewText().Title("Anything else I should know?").Value(&freeform), ).WithHideFunc(notSubmitting), ).WithTheme(huh.ThemeBase()) if err := form.Run(); err != nil { return err } if !submitting { fmt.Println("ok! just run the command again if you change your mind~") return nil } sd := submitData{ Username: o.Username, Live: live, When: talkWhen, Length: length, Desc: desc, Freeform: freeform, Interactive: interactive, } if err = sendSubmitEmail(sd); err != nil { return err } fmt.Println("THANK YOU!! I'll be in touch~") return nil } type jamData struct { Username string Desc string Physical bool } func jam(o opts) error { jamContent, err := md.ReadFile("md/jam.md") if err != nil { return err } out, err := glamour.Render(string(jamContent), "auto") if err != nil { return err } fmt.Println(out) submitting := true var desc string var physical bool notSubmitting := func() bool { return !submitting } form := huh.NewForm( huh.NewGroup( huh.NewConfirm().Title("So...wanna propose a jam?").Value(&submitting), ), huh.NewGroup( huh.NewConfirm().Title("Excellent. Does it require being in person?").Value(&physical), ).WithHideFunc(notSubmitting), huh.NewGroup( huh.NewText().Title("Describe the jam."). Description("Include details like required knowledge or tools").Value(&desc), ).WithHideFunc(notSubmitting), ).WithTheme(huh.ThemeBase()) if err := form.Run(); err != nil { return err } if !submitting { fmt.Println("ok! just run the command again if you change your mind~") return nil } jd := jamData{ Username: o.Username, Desc: desc, Physical: physical, } if err = sendJamEmail(jd); err != nil { return err } fmt.Println("THANK YOU!! I'll be in touch~") return nil } func sendJamEmail(jd jamData) error { bs, err := yaml.Marshal(jd) if err != nil { return err } return email.SendLocalEmail("vilmibm", "JAMPROP TOWNCON24", string(bs)) } type opts struct { Username string } func _main() error { u, err := user.Current() if err != nil { return err } o := opts{ Username: u.Username, } b, err := banner() if err != nil { return err } fmt.Println(b) fmt.Print(fmt.Sprintf("\t\t^_^_^_^_^_^_^ hi ~%s ^_^_^_^_^_^_^\n", o.Username)) fmt.Println() var mode string // NB: this could be a standalone prompt but when it is, ctrl+c behavior is goofed up. form := huh.NewForm( huh.NewGroup( huh.NewSelect[string]().Title("whadduyu wanna doo?"). Options( huh.NewOption("Get an info dump", "info"), huh.NewOption("SEE SCHEDULE", "schedule"), huh.NewOption("RSVP", "rsvp"), huh.NewOption("Submit or propose a work (talk, art, etc)", "submit"), huh.NewOption("Propose a creative jam", "jam"), huh.NewOption("Quit", "quit"), ).Value(&mode))).WithTheme(huh.ThemeBase()) err = form.Run() if err != nil { return err } switch mode { case "info": return info() case "schedule": return schedule() case "rsvp": return rsvp(o) case "submit": return submit(o) case "jam": return jam(o) case "quit": default: return fmt.Errorf("wtf '%s'", mode) } return nil } func main() { if err := _main(); err != nil { fmt.Fprintf(os.Stderr, ":( failed: %s\n", err.Error()) os.Exit(1) } }