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
|
||||
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.
|
||||
|
|
@ -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": [
|
||||
{
|
||||
"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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue