Repo Reorg
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
package email
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"github.com/dustinpianalto/goff/pkg/puzzles"
|
||||
imap "github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/client"
|
||||
"github.com/emersion/go-message/mail"
|
||||
)
|
||||
|
||||
const ()
|
||||
|
||||
var (
|
||||
emailUsername = os.Getenv("GOFF_EMAIL_USERNAME")
|
||||
emailPassword = os.Getenv("GOFF_EMAIL_PASSWORD")
|
||||
puzzleAddress = mail.Address{
|
||||
Name: "Daily Coding Problem",
|
||||
Address: "founders@dailycodingproblem.com",
|
||||
}
|
||||
)
|
||||
|
||||
var EmailClient client.Client
|
||||
|
||||
func RecieveEmail(dg *discordgo.Session) {
|
||||
for {
|
||||
log.Println("Connecting to Email server.")
|
||||
|
||||
EmailClient, err := client.DialTLS("mail.djpianalto.com:993", nil)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
if err = EmailClient.Login(emailUsername, emailPassword); err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
log.Println("Connected to Email server.")
|
||||
|
||||
mbox, err := EmailClient.Select("INBOX", false)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
if mbox.Messages == 0 {
|
||||
log.Println("No Messages in Mailbox")
|
||||
}
|
||||
|
||||
criteria := imap.NewSearchCriteria()
|
||||
criteria.WithoutFlags = []string{"\\Seen"}
|
||||
uids, err := EmailClient.Search(criteria)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
if len(uids) > 0 {
|
||||
seqset := new(imap.SeqSet)
|
||||
seqset.AddNum(uids...)
|
||||
section := &imap.BodySectionName{}
|
||||
items := []imap.FetchItem{section.FetchItem()}
|
||||
messages := make(chan *imap.Message, 10)
|
||||
go func() {
|
||||
if err = EmailClient.Fetch(seqset, items, messages); err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for msg := range messages {
|
||||
if msg == nil {
|
||||
log.Println("No New Messages")
|
||||
continue
|
||||
}
|
||||
r := msg.GetBody(section)
|
||||
if r == nil {
|
||||
log.Println("Server didn't send a message body")
|
||||
continue
|
||||
}
|
||||
wg.Add(1)
|
||||
go processEmail(r, dg, &wg)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
EmailClient.Logout()
|
||||
time.Sleep(300 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func processEmail(r io.Reader, dg *discordgo.Session, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
mr, err := mail.CreateReader(r)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
header := mr.Header
|
||||
from, err := header.AddressList("From")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
subject, err := header.Subject()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
log.Println(from)
|
||||
log.Println(subject)
|
||||
if addressIn(from, puzzleAddress) &&
|
||||
strings.Contains(subject, "Daily Coding Problem:") {
|
||||
log.Println("Processing Puzzle")
|
||||
puzzles.ProcessPuzzleEmail(mr, dg)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func addressIn(s []*mail.Address, a mail.Address) bool {
|
||||
for _, item := range s {
|
||||
if item.String() == a.String() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package puzzles
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"github.com/dustinpianalto/goff"
|
||||
"github.com/dustinpianalto/goff/internal/postgres"
|
||||
"github.com/emersion/go-message/mail"
|
||||
)
|
||||
|
||||
func ProcessPuzzleEmail(mr *mail.Reader, dg *discordgo.Session) {
|
||||
var body []byte
|
||||
for {
|
||||
p, err := mr.NextPart()
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
log.Println(err)
|
||||
break
|
||||
}
|
||||
|
||||
switch h := p.Header.(type) {
|
||||
case *mail.InlineHeader:
|
||||
// This is the message's text (can be plain-text or HTML)
|
||||
if t, _, _ := h.ContentType(); t == "text/plain" {
|
||||
body, _ = ioutil.ReadAll(p.Body)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(body) > 0 {
|
||||
s := string(body)
|
||||
puzzle := strings.Split(s, "----------")[0]
|
||||
date, err := mr.Header.Date()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
e := discordgo.MessageEmbed{
|
||||
Title: "Daily Coding Problem",
|
||||
URL: "https://dailycodingproblem.com/",
|
||||
Description: "```" + puzzle + "```",
|
||||
Timestamp: date.Format(time.RFC3339),
|
||||
Footer: &discordgo.MessageEmbedFooter{
|
||||
Text: "Daily Coding Problem",
|
||||
},
|
||||
}
|
||||
var guilds []goff.Guild
|
||||
queryString := `SELECT id, puzzle_channel, puzzle_role from guilds`
|
||||
rows, err := postgres.DB.Query(queryString)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
for rows.Next() {
|
||||
var guild goff.Guild
|
||||
err := rows.Scan(&guild.ID, &guild.PuzzleChannel, &guild.PuzzleRole)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
guilds = append(guilds, guild)
|
||||
}
|
||||
var puzzleID int64
|
||||
queryString = "INSERT INTO puzzles (text, time) VALUES ($1, $2) RETURNING id"
|
||||
err = postgres.DB.QueryRow(queryString, puzzle, date).Scan(&puzzleID)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
for _, g := range guilds {
|
||||
if g.PuzzleChannel == "" {
|
||||
continue
|
||||
}
|
||||
var msg discordgo.MessageSend
|
||||
role, err := dg.State.Role(g.ID, g.PuzzleRole.String)
|
||||
if err != nil {
|
||||
msg = discordgo.MessageSend{
|
||||
Embed: &e,
|
||||
}
|
||||
} else {
|
||||
msg = discordgo.MessageSend{
|
||||
Content: role.Mention(),
|
||||
Embed: &e,
|
||||
}
|
||||
}
|
||||
m, err := dg.ChannelMessageSendComplex(g.PuzzleChannel, &msg)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
queryString = "INSERT INTO x_guilds_puzzles (guild_id, puzzle_id, message_id) VALUES ($1, $2, $3)"
|
||||
_, err = postgres.DB.Exec(queryString, g.ID, puzzleID, m.ID)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Operator struct {
|
||||
Token string
|
||||
Precedence int
|
||||
Association string
|
||||
}
|
||||
|
||||
func (o Operator) HasHigherPrecedence(t Operator) bool {
|
||||
return o.Precedence < t.Precedence // lower number is higher precedence
|
||||
}
|
||||
|
||||
func (o Operator) HasEqualPrecedence(t Operator) bool {
|
||||
return o.Precedence == t.Precedence
|
||||
}
|
||||
|
||||
func (o Operator) IsLeftAssociative() bool {
|
||||
return o.Association == "left"
|
||||
}
|
||||
|
||||
var operators = map[string]Operator{
|
||||
"+": Operator{
|
||||
Token: "+",
|
||||
Precedence: 4,
|
||||
Association: "left",
|
||||
},
|
||||
"-": Operator{
|
||||
Token: "-",
|
||||
Precedence: 4,
|
||||
Association: "left",
|
||||
},
|
||||
"*": Operator{
|
||||
Token: "*",
|
||||
Precedence: 3,
|
||||
Association: "left",
|
||||
},
|
||||
"/": Operator{
|
||||
Token: "/",
|
||||
Precedence: 3,
|
||||
Association: "left",
|
||||
},
|
||||
"%": Operator{
|
||||
Token: "%",
|
||||
Precedence: 3,
|
||||
Association: "left",
|
||||
},
|
||||
"(": Operator{
|
||||
Token: "(",
|
||||
Precedence: 1,
|
||||
Association: "left",
|
||||
},
|
||||
")": Operator{
|
||||
Token: ")",
|
||||
Precedence: 1,
|
||||
Association: "left",
|
||||
},
|
||||
}
|
||||
|
||||
type Stack []Operator
|
||||
|
||||
func (s *Stack) IsEmpty() bool {
|
||||
return len(*s) == 0
|
||||
}
|
||||
|
||||
func (s *Stack) Push(op Operator) {
|
||||
*s = append(*s, op)
|
||||
}
|
||||
|
||||
func (s *Stack) Pop() (Operator, bool) {
|
||||
if s.IsEmpty() {
|
||||
return Operator{}, false
|
||||
}
|
||||
index := len(*s) - 1
|
||||
element := (*s)[index]
|
||||
*s = (*s)[:index]
|
||||
return element, true
|
||||
}
|
||||
|
||||
func (s *Stack) Top() Operator {
|
||||
if s.IsEmpty() {
|
||||
return Operator{}
|
||||
}
|
||||
return (*s)[len(*s)-1]
|
||||
}
|
||||
|
||||
func GenerateRPN(tokens []string) (string, error) {
|
||||
output := ""
|
||||
s := Stack{}
|
||||
for _, token := range tokens {
|
||||
err := processToken(token, &s, &output)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
for !s.IsEmpty() {
|
||||
ele, _ := s.Pop()
|
||||
output += " " + ele.Token
|
||||
}
|
||||
|
||||
return strings.TrimSpace(output), nil
|
||||
}
|
||||
|
||||
func processToken(t string, s *Stack, o *string) error {
|
||||
if _, err := strconv.Atoi(t); err == nil {
|
||||
*o += " " + t
|
||||
return nil
|
||||
} else if op, ok := operators[t]; ok {
|
||||
if op.Token == "(" {
|
||||
s.Push(op)
|
||||
} else if op.Token == ")" {
|
||||
if s.IsEmpty() {
|
||||
return fmt.Errorf("mismatched parentheses")
|
||||
}
|
||||
for s.Top().Token != "(" {
|
||||
if ele, ok := s.Pop(); ok {
|
||||
*o += " " + ele.Token
|
||||
} else {
|
||||
return fmt.Errorf("mismatched parentheses")
|
||||
}
|
||||
if s.IsEmpty() {
|
||||
break
|
||||
}
|
||||
}
|
||||
s.Pop() // Pop and discard the (
|
||||
} else if !s.IsEmpty() {
|
||||
for {
|
||||
if (s.Top().HasHigherPrecedence(op) ||
|
||||
(s.Top().HasEqualPrecedence(op) &&
|
||||
op.IsLeftAssociative())) &&
|
||||
s.Top().Token != "(" {
|
||||
if ele, ok := s.Pop(); ok {
|
||||
*o += " " + ele.Token
|
||||
if s.IsEmpty() {
|
||||
break
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
s.Push(op)
|
||||
} else {
|
||||
s.Push(op)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("invalid character %s", t)
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type FStack []float64
|
||||
|
||||
func (s *FStack) IsEmpty() bool {
|
||||
return len(*s) == 0
|
||||
}
|
||||
|
||||
func (s *FStack) Push(op float64) {
|
||||
*s = append(*s, op)
|
||||
}
|
||||
|
||||
func (s *FStack) Pop() (float64, bool) {
|
||||
if s.IsEmpty() {
|
||||
return 0, false
|
||||
}
|
||||
index := len(*s) - 1
|
||||
element := (*s)[index]
|
||||
*s = (*s)[:index]
|
||||
return element, true
|
||||
}
|
||||
|
||||
func (s *FStack) PopTwo() (float64, float64, bool) {
|
||||
if s.IsEmpty() || len(*s) < 2 {
|
||||
return 0, 0, false
|
||||
}
|
||||
index := len(*s) - 1
|
||||
b := (*s)[index]
|
||||
a := (*s)[index-1]
|
||||
*s = (*s)[:index-1]
|
||||
return a, b, true
|
||||
|
||||
}
|
||||
|
||||
func (s *FStack) Top() float64 {
|
||||
if s.IsEmpty() {
|
||||
return 0
|
||||
}
|
||||
return (*s)[len(*s)-1]
|
||||
}
|
||||
|
||||
func ParseRPN(args []string) (float64, error) {
|
||||
s := FStack{}
|
||||
for _, token := range args {
|
||||
switch token {
|
||||
case "+":
|
||||
if a, b, ok := s.PopTwo(); ok {
|
||||
s.Push(a + b)
|
||||
} else {
|
||||
return 0, fmt.Errorf("not enough operands on stack for +: %v", s)
|
||||
}
|
||||
case "-":
|
||||
if a, b, ok := s.PopTwo(); ok {
|
||||
s.Push(a - b)
|
||||
} else {
|
||||
return 0, fmt.Errorf("not enough operands on stack for -: %v", s)
|
||||
}
|
||||
case "*":
|
||||
if a, b, ok := s.PopTwo(); ok {
|
||||
s.Push(a * b)
|
||||
} else {
|
||||
return 0, fmt.Errorf("not enough operands on stack for *: %v", s)
|
||||
}
|
||||
case "/":
|
||||
if a, b, ok := s.PopTwo(); ok {
|
||||
s.Push(a / b)
|
||||
} else {
|
||||
return 0, fmt.Errorf("not enough operands on stack for /: %v", s)
|
||||
}
|
||||
case "%":
|
||||
if a, b, ok := s.PopTwo(); ok {
|
||||
s.Push(math.Mod(a, b))
|
||||
} else {
|
||||
return 0, fmt.Errorf("not enough operands on stack for %: %v", s)
|
||||
}
|
||||
default:
|
||||
f, err := strconv.ParseFloat(token, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
s.Push(f)
|
||||
}
|
||||
}
|
||||
if res, ok := s.Pop(); ok {
|
||||
return res, nil
|
||||
}
|
||||
return 0, errors.New("no result")
|
||||
}
|
||||
Reference in New Issue
Block a user