From 605d1c69ec38d76f575c1085472d2c4a6485db59 Mon Sep 17 00:00:00 2001 From: Harry Culpan Date: Sat, 20 Jan 2024 22:19:42 -0500 Subject: [PATCH] Updated README and example players.json --- README.md | 63 +++++++++++++++++++++- cmd/cli/main.go.bak | 123 ------------------------------------------- exmaple_players.json | 8 ++- 3 files changed, 67 insertions(+), 127 deletions(-) delete mode 100644 cmd/cli/main.go.bak diff --git a/README.md b/README.md index 0965b42..df443d5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,61 @@ -# areweplaying -A Go web app to track if players are going to attend the next session of my game +# Are We Playing? +A Go web app to track if players are going to attend the next session of my TTPRG. + +The purpose of this app is for my players to indicate if they'll attend the game +with as little effort as possible. To this end, I didn't want to have a site that +they'd have to log into, and I'd prefer they don't have to even go to the website +unless they want to. So players can interace with this app using email only. + +It consists of two components, a web app and a CLI. For players, the interface is +very simple. It just presents a list of players, with an indication of whether or +not they will attend. They can click to update this to say that they plan to attend +or that they will not attend. There is no security here, as the only thing that +anyone can do is update attendance. No personal information, other than player names +(presumably first names) will be displayed or updated here. Players are configured +manually by me by directly editing the json file that is the only data store for +this project. + +This web app does have a "Setup" screen that can do some admin, like send a +game reminder email to all the players, or advance to the next session date. This +does require a password to login (though not a username), and I should be the only +one to ever see this. The security on this app is by no means ironclad, but it's +pretty standard. Once I login, it saves a JWT token as a cookie and every page +requires this token. + +The second component is a CLI, and it just checks an email inbox to see if any of +the players have responded to the game reminder email with a "yes" or "no", indicating +if they will attend. The CLI runs as a cron job, and if it sees a new email from +one of the player's emails, it looks for a "yes" or "no" in the first non-blank line. +If it finds this, it updates the player's status accordingly. + +# Development + +This app has a number of dependencies. It uses the Chi router and HTMX for the +web interface with Bootstrap 5 for styling. And it uses ```templ``` for templating +the HTML. + +The data store is just a json file, and an example json file is included. By default, +it stores data to ```players.json``` in the working directory. + +It does use Godotenv for configuration, and Cobra for the CLI. + +The only other significant library is ```go-imap``` to simplify interfacing with +IMAP and SMTP servers. + +I also used Air during development for hot restarts, so there's an ```.air.toml``` +file in the project. This is just for the web app; it is not configured to work with +the CLI app. + +# Building + +```make build``` + +This will produce two executables: +```areweplaying``` - The web app +```awp-cli``` - The CLI app, obviously + +I've also included a command to make Linux/AMD64 versions of the files: + +```make linuxbuild``` + +This will produce the same two executables, but with a ```.linux``` extension. \ No newline at end of file diff --git a/cmd/cli/main.go.bak b/cmd/cli/main.go.bak deleted file mode 100644 index 7b7f9f7..0000000 --- a/cmd/cli/main.go.bak +++ /dev/null @@ -1,123 +0,0 @@ -package main - -import ( - "fmt" - "log" - "net/smtp" - "os" - "strconv" - - "github.com/emersion/go-imap" - "github.com/emersion/go-imap/client" - "github.com/joho/godotenv" -) - -func main() { - var username string - var password string - var fetchServer string - var sendServer string - - err := godotenv.Load() - if err != nil { - log.Fatalf("unable to load env: %s", err) - } - - username = os.Getenv("USERNAME") - password = os.Getenv("PASSWORD") - fetchServer = os.Getenv("IMAP_SERVER") - sendServer = os.Getenv("SMTP_SERVER") - port := 993 - - if len(username) == 0 || len(password) == 0 || len(fetchServer) == 0 || len(sendServer) == 0 { - log.Fatal("missing configuration variables") - } - - imapClient, err := connectToServer(username, password, fetchServer, port) - if err != nil { - log.Fatal(err) - } - defer imapClient.Logout() - - if err := fetchEmails(imapClient); err != nil { - log.Fatal(err) - } - - /* - to := "harry@culpan.org" - subject := "Test Email" - body := "This is a test email sent from a Go-based email client." - - if err := sendEmail(username, password, sendServer, 587, to, subject, body); err != nil { - log.Fatal(err) - } - */ -} - -func fetchEmails(imapClient *client.Client) error { - // Select the mailbox you want to read - _, err := imapClient.Select("INBOX", false) - if err != nil { - return err - } - - criteria := imap.NewSearchCriteria() - criteria.WithoutFlags = []string{"\\Seen"} - uids, err := imapClient.Search(criteria) - if err != nil { - log.Printf("Search error: %s\n", err) - } - - if len(uids) > 0 { - // Define the range of emails to fetch - seqSet := new(imap.SeqSet) - seqSet.AddNum(uids...) - - // Fetch the required message attributes - messages := make(chan *imap.Message, 10) - section := &imap.BodySectionName{} - items := []imap.FetchItem{section.FetchItem(), imap.FetchEnvelope} - - go func() { - if err := imapClient.Fetch(seqSet, items, messages); err != nil { - log.Fatal("Fetch error: " + err.Error()) - } - }() - - for msg := range messages { - fmt.Println("Subject:", msg.Envelope.Subject) - } - } - - return nil -} - -func sendEmail(username, password, server string, port int, to, subject, body string) error { - auth := smtp.PlainAuth("", username, password, server) - - msg := "From: " + username + "\r\n" + - "To: " + to + "\r\n" + - "Subject: " + subject + "\r\n" + - "\r\n" + - body - - err := smtp.SendMail(server+":"+strconv.Itoa(port), auth, username, []string{to}, []byte(msg)) - if err != nil { - return err - } - - return nil -} - -func connectToServer(username, password, server string, port int) (*client.Client, error) { - c, err := client.DialTLS(fmt.Sprintf("%s:%d", server, port), nil) - if err != nil { - return nil, err - } - - if err := c.Login(username, password); err != nil { - return nil, err - } - - return c, nil -} diff --git a/exmaple_players.json b/exmaple_players.json index 84b9d1d..3969714 100644 --- a/exmaple_players.json +++ b/exmaple_players.json @@ -3,11 +3,15 @@ "players": [ { "name": "Player1", - "attending": "unknown" + "attending": "unknown", + "email": "somebody@somehost.com", + "gm": true }, { "name": "Player2", - "attending": "yes" + "attending": "yes", + "email": "somebodyelse@somehost.com", + "gm": false } ], "inc_days": 7,