Updated README and example players.json

This commit is contained in:
Harry Culpan 2024-01-20 22:19:42 -05:00
parent 9f894671b0
commit 605d1c69ec
3 changed files with 67 additions and 127 deletions

View file

@ -1,2 +1,61 @@
# areweplaying # Are We Playing?
A Go web app to track if players are going to attend the next session of my game 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.

View file

@ -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
}

View file

@ -3,11 +3,15 @@
"players": [ "players": [
{ {
"name": "Player1", "name": "Player1",
"attending": "unknown" "attending": "unknown",
"email": "somebody@somehost.com",
"gm": true
}, },
{ {
"name": "Player2", "name": "Player2",
"attending": "yes" "attending": "yes",
"email": "somebodyelse@somehost.com",
"gm": false
} }
], ],
"inc_days": 7, "inc_days": 7,