Updated README and example players.json
This commit is contained in:
parent
9f894671b0
commit
605d1c69ec
3 changed files with 67 additions and 127 deletions
63
README.md
63
README.md
|
|
@ -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.
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue