Reorganize and update Dockerfile

This commit is contained in:
Dustin Pianalto
2020-08-30 00:08:40 -08:00
parent 3d44258370
commit 19d173af0a
24 changed files with 2672 additions and 8 deletions
+154
View File
@@ -0,0 +1,154 @@
package utils
import (
"database/sql"
"fmt"
"log"
_ "github.com/lib/pq"
)
var (
Database *sql.DB
)
func ConnectDatabase(dbConnString string) {
db, err := sql.Open("postgres", dbConnString)
if err != nil {
panic(fmt.Sprintf("Can't connect to the database. %v", err))
} else {
fmt.Println("Database Connected.")
}
Database = db
}
func InitializeDatabase() {
_, err := Database.Query("CREATE TABLE IF NOT EXISTS users(" +
"id varchar(30) primary key," +
"banned bool not null default false," +
"logging bool not null default true," +
"steam_id varchar(30) NOT NULL DEFAULT ''," +
"is_active bool not null default true," +
"is_staff bool not null default false," +
"is_admin bool not null default false" +
")")
if err != nil {
fmt.Println(err)
}
_, err = Database.Query("CREATE TABLE IF NOT EXISTS guilds(" +
"id varchar(30) primary key," +
"welcome_message varchar(1000) NOT NULL DEFAULT ''," +
"goodbye_message varchar(1000) NOT NULL DEFAULT ''," +
"logging_channel varchar(30) NOT NULL DEFAULT ''," +
"welcome_channel varchar(30) NOT NULL DEFAULT ''" +
")")
if err != nil {
fmt.Println(err)
}
_, err = Database.Query("CREATE TABLE IF NOT EXISTS prefixes(" +
"id serial primary key," +
"prefix varchar(10) not null unique default 'Go.'" +
")")
if err != nil {
fmt.Println(err)
}
_, err = Database.Query("CREATE TABLE IF NOT EXISTS tags(" +
"id serial primary key," +
"tag varchar(100) not null unique," +
"content varchar(1000) not null," +
"creator varchar(30) not null references users(id)," +
"creation_time timestamp not null default NOW()," +
"guild_id varchar(30) not null references guilds(id)" +
")")
if err != nil {
fmt.Println(err)
}
_, err = Database.Query("CREATE TABLE IF NOT EXISTS x_users_guilds(" +
"guild_id varchar(30) not null references guilds(id)," +
"user_id varchar(30) not null references users(id)" +
")")
if err != nil {
fmt.Println(err)
}
_, err = Database.Query("CREATE TABLE IF NOT EXISTS x_guilds_prefixes(" +
"guild_id varchar(30) not null references guilds(id)," +
"prefix_id int not null references prefixes(id)" +
")")
if err != nil {
fmt.Println(err)
}
_, err = Database.Query("CREATE TABLE IF NOT EXISTS tasks(" +
"id serial primary key," +
"type varchar(10) not null," +
"content text not null," +
"guild_id varchar(30) not null references guilds(id)," +
"channel_id varchar(30) not null," +
"user_id varchar(30) not null," +
"creation_time timestamp not null default NOW()," +
"trigger_time timestamp not null," +
"completed bool not null default false," +
"processing bool default false)")
if err != nil {
fmt.Println(err)
}
_, err = Database.Query(`CREATE TABLE IF NOT EXISTS postfixes(
id serial primary key,
name varchar(100) not null,
time timestamp not null default NOW())`)
if err != nil {
log.Println(err)
}
_, err = Database.Exec(`CREATE TABLE IF NOT EXISTS puzzles(
id serial primary key,
text text not null,
time timestamp not null
)`)
if err != nil {
log.Println(err)
}
_, err = Database.Exec(`CREATE TABLE IF NOT EXISTS x_guilds_puzzles(
id serial primary key,
guild_id varchar(30) not null references guilds(id),
puzzle_id int not null references puzzles(id),
message_id varchar(30) not null
)`)
RunPostfixes()
}
func LoadTestData() {
_, err := Database.Query("INSERT INTO users (id, banned, logging, steam_id, is_active, is_staff, is_admin) values " +
"('351794468870946827', false, true, '76561198024193239', true, true, true)," +
"('692908139506434065', false, true, '', true, false, false)," +
"('396588996706304010', false, true, '', true, true, false)")
if err != nil {
fmt.Println(err)
}
_, err = Database.Query("INSERT INTO guilds (id, welcome_message, goodbye_message) VALUES " +
"('265828729970753537', 'Hey there is someone new here.', 'Well fine then... Just leave without saying goodbye')")
if err != nil {
fmt.Println(err)
}
_, err = Database.Query("INSERT INTO prefixes (prefix) VALUES ('Godev.'), ('godev.'), ('godev,')")
if err != nil {
fmt.Println(err)
}
_, err = Database.Query("INSERT INTO x_users_guilds (guild_id, user_id) VALUES " +
"('265828729970753537', '351794468870946827')," +
"('265828729970753537', '692908139506434065')," +
"('265828729970753537', '396588996706304010')")
if err != nil {
fmt.Println(err)
}
_, err = Database.Query("INSERT INTO x_guilds_prefixes (guild_id, prefix_id) VALUES " +
"('265828729970753537', 1)," +
"('265828729970753537', 2)," +
"('265828729970753537', 3)")
if err != nil {
fmt.Println(err)
}
_, err = Database.Query("INSERT INTO tags (tag, content, creator, guild_id) VALUES " +
"('test', 'This is a test of the tag system', '351794468870946827', '265828729970753537')")
if err != nil {
fmt.Println(err)
}
}
+64
View File
@@ -0,0 +1,64 @@
package utils
import (
"fmt"
"time"
)
func ParseDateString(inTime time.Time) string {
d := time.Now().Sub(inTime)
s := int64(d.Seconds())
days := s / 86400
s = s - (days * 86400)
hours := s / 3600
s = s - (hours * 3600)
minutes := s / 60
seconds := s - (minutes * 60)
dateString := ""
if days != 0 {
dateString += fmt.Sprintf("%v days ", days)
}
if hours != 0 {
dateString += fmt.Sprintf("%v hours ", hours)
}
if minutes != 0 {
dateString += fmt.Sprintf("%v minutes ", minutes)
}
if seconds != 0 {
dateString += fmt.Sprintf("%v seconds ", seconds)
}
if dateString != "" {
dateString += " ago."
} else {
dateString = "Now"
}
stamp := inTime.Format("2006-01-02 15:04:05")
return fmt.Sprintf("%v\n%v", dateString, stamp)
}
func ParseDurationString(inDur time.Duration) string {
s := int64(inDur.Seconds())
days := s / 86400
s = s - (days * 86400)
hours := s / 3600
s = s - (hours * 3600)
minutes := s / 60
seconds := s - (minutes * 60)
durString := ""
if days != 0 {
durString += fmt.Sprintf("%v days ", days)
}
if hours != 0 {
durString += fmt.Sprintf("%v hours ", hours)
}
if minutes != 0 {
durString += fmt.Sprintf("%v minutes ", minutes)
}
if seconds != 0 {
durString += fmt.Sprintf("%v seconds ", seconds)
}
if durString == "" {
durString = "0 seconds"
}
return fmt.Sprintf("%v", durString)
}
+132
View File
@@ -0,0 +1,132 @@
package utils
import (
"io"
"log"
"os"
"strings"
"sync"
"time"
"github.com/bwmarrin/discordgo"
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")
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
}
+34
View File
@@ -0,0 +1,34 @@
package utils
import (
"fmt"
"github.com/bwmarrin/discordgo"
)
var LoggingChannel = make(chan *LogEvent, 10)
type LogEvent struct {
// Embed with log message
Embed discordgo.MessageEmbed
// Guild to log event in
GuildID string
// Discordgo Session. Needed for sending messages
Session *discordgo.Session
}
func LoggingHandler(lc chan *LogEvent) {
for event := range lc {
var channelID string
row := Database.QueryRow("SELECT logging_channel FROM guilds where id=$1", event.GuildID)
err := row.Scan(&channelID)
if err != nil {
fmt.Println(err)
return
}
if channelID == "" {
return
}
_, _ = event.Session.ChannelMessageSendEmbed(channelID, &event.Embed)
}
}
+77
View File
@@ -0,0 +1,77 @@
package utils
import "log"
type postfix struct {
Name string
Invoke func(bool) error
}
var postfixes = []postfix{
postfix{
Name: "1_Update_Guild_for_Puzzle",
Invoke: updateGuildForPuzzle,
},
postfix{
Name: "1_Update_X_Guild_Prefixes_to_add_ID",
Invoke: updateXGuildPrefixesToAddID,
},
}
func RunPostfixes() {
for _, postfix := range postfixes {
queryString := "SELECT * from postfixes where name = $1"
rows, err := Database.Query(queryString, postfix.Name)
if err != nil {
log.Println(err)
continue
}
if rows.Next() {
continue
} else {
err := postfix.Invoke(false)
if err != nil {
continue
}
_, err = Database.Exec("INSERT INTO postfixes (name) VALUES ($1)", postfix.Name)
if err != nil {
log.Println(err)
continue
}
}
}
}
func updateGuildForPuzzle(revert bool) error {
var queryString string
if !revert {
queryString = `ALTER TABLE guilds
ADD COLUMN puzzle_channel varchar(30) not null default ''`
} else {
queryString = `ALTER TABLE guilds
DROP COLUMN puzzleChat`
}
_, err := Database.Exec(queryString)
if err != nil {
log.Println(err)
return err
}
return nil
}
func updateXGuildPrefixesToAddID(revert bool) error {
var queryString string
if !revert {
queryString = `ALTER TABLE x_guilds_prefixes
ADD COLUMN id serial primary key`
} else {
queryString = `ALTER TABLE x_guilds_prefixes
DROP COLUMN id`
}
_, err := Database.Exec(queryString)
if err != nil {
log.Println(err)
return err
}
return nil
}
+93
View File
@@ -0,0 +1,93 @@
package utils
import (
"io"
"io/ioutil"
"log"
"strings"
"time"
"github.com/bwmarrin/discordgo"
"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 []Guild
queryString := `SELECT id, puzzle_channel from guilds`
rows, err := Database.Query(queryString)
if err != nil {
log.Println(err)
}
for rows.Next() {
var guild Guild
err := rows.Scan(&guild.ID, &guild.PuzzleChannel)
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 = Database.QueryRow(queryString, puzzle, date).Scan(&puzzleID)
if err != nil {
log.Println(err)
return
}
for _, g := range guilds {
if g.PuzzleChannel == "" {
continue
}
msg := discordgo.MessageSend{
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 = Database.Exec(queryString, g.ID, puzzleID, m.ID)
if err != nil {
log.Println(err)
continue
}
}
}
}
+156
View File
@@ -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)
}
+95
View File
@@ -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")
}
+32
View File
@@ -0,0 +1,32 @@
package utils
import "time"
type Snowflake struct {
CreationTime time.Time
WorkerID int8
ProcessID int8
Increment int16
}
func ParseSnowflake(s int64) Snowflake {
const (
DISCORD_EPOCH = 1420070400000
TIME_BITS_LOC = 22
WORKER_ID_LOC = 17
WORKER_ID_MASK = 0x3E0000
PROCESS_ID_LOC = 12
PROCESS_ID_MASK = 0x1F000
INCREMENT_MASK = 0xFFF
)
creationTime := time.Unix(((s>>TIME_BITS_LOC)+DISCORD_EPOCH)/1000.0, 0)
workerID := (s & WORKER_ID_MASK) >> WORKER_ID_LOC
processID := (s & PROCESS_ID_MASK) >> PROCESS_ID_LOC
increment := s & INCREMENT_MASK
return Snowflake{
CreationTime: creationTime,
WorkerID: int8(workerID),
ProcessID: int8(processID),
Increment: int16(increment),
}
}
+132
View File
@@ -0,0 +1,132 @@
package utils
import (
"fmt"
"github.com/bwmarrin/discordgo"
"log"
"time"
)
type Task struct {
ID int64
Type string
Content string
GuildID string
ChannelID string
UserID string
CreationTime time.Time
TriggerTime time.Time
}
func processTask(task *Task, s *discordgo.Session) {
query := "SELECT completed, processing from tasks where id = $1"
res, err := Database.Query(query, task.ID)
if err != nil {
log.Println(err)
return
}
var completed bool
var processing bool
res.Next()
err = res.Scan(&completed, &processing)
if err != nil {
log.Println(err)
return
}
if completed || processing {
return
}
closeQuery := "Update tasks set completed = true where id = $1"
processQuery := "UPDATE tasks SET processing = true WHERE id = $1"
defer Database.Exec(closeQuery, task.ID)
_, err = Database.Exec(processQuery, task.ID)
if err != nil {
log.Println(err)
return
}
log.Println(fmt.Sprintf("Processing task %v", task.ID))
guild, err := s.Guild(task.GuildID)
if err != nil {
log.Print(fmt.Sprintf("Can't find guild with ID %v. Canceling task %v.", task.GuildID, task.ID))
return
}
channel, err := s.Channel(task.ChannelID)
if err != nil {
log.Print(fmt.Sprintf("Can't find channel with ID %v. Canceling task %v.", task.ChannelID, task.ID))
return
}
if channel.GuildID != guild.ID {
log.Print(fmt.Sprintf("The channel %v is not in guild %v. Canceling task %v.", channel.Name, guild.Name, task.ID))
return
}
member, err := s.GuildMember(guild.ID, task.UserID)
if err != nil {
log.Print(fmt.Sprintf("Can't find user with ID %v in guild %v. Canceling task %v.", task.UserID, guild.Name, task.ID))
return
}
if task.Type == "Reminder" {
color := s.State.UserColor(member.User.ID, channel.ID)
e := discordgo.MessageEmbed{
Title: "REMINDER",
Description: task.Content,
Timestamp: task.CreationTime.Format(time.RFC3339),
Color: color,
Footer: &discordgo.MessageEmbedFooter{
Text: "Created: ",
},
}
msg := discordgo.MessageSend{
Content: member.Mention(),
Embed: &e,
}
_, err = s.ChannelMessageSendComplex(channel.ID, &msg)
if err != nil {
log.Println(err)
}
}
processQuery = "UPDATE tasks SET processing = false WHERE id = $1"
_, err = Database.Exec(processQuery, task.ID)
if err != nil {
log.Println(err)
}
}
func getTasksToRun() []Task {
query := "SELECT id, type, content, guild_id, channel_id, user_id, creation_time, trigger_time " +
"from tasks where completed is false and processing is false and trigger_time < $1"
res, err := Database.Query(query, time.Now())
if err != nil {
log.Println(err)
}
var tasks []Task
for res.Next() {
var t Task
err = res.Scan(&t.ID, &t.Type, &t.Content, &t.GuildID, &t.ChannelID, &t.UserID, &t.CreationTime, &t.TriggerTime)
if err != nil {
log.Println(err)
}
for _, task := range tasks {
if task.ID == t.ID {
continue
}
}
tasks = append(tasks, t)
}
return tasks
}
func ProcessTasks(s *discordgo.Session, interval int) {
for {
time.Sleep(time.Duration(interval * 1e9))
tasks := getTasksToRun()
if len(tasks) > 0 {
for _, t := range tasks {
go processTask(&t, s)
}
}
}
}
+10
View File
@@ -0,0 +1,10 @@
package utils
type Guild struct {
ID string
WelcomeMessage string
GoodbyeMessage string
LoggingChannel string
WelcomeChannel string
PuzzleChannel string
}