diff --git a/helpers.go b/helpers.go index 94d5063..dcd6f93 100644 --- a/helpers.go +++ b/helpers.go @@ -13,7 +13,6 @@ import ( "strings" "time" - ics "github.com/arran4/golang-ical" "github.com/bwmarrin/discordgo" "github.com/rs/zerolog" log "github.com/rs/zerolog/log" @@ -321,16 +320,6 @@ func GetFooter(time time.Time) *discordgo.MessageEmbedFooter { } } -func NewCalendar() *ics.Calendar { - c := &ics.Calendar{ - Components: []ics.Component{}, - CalendarProperties: []ics.CalendarProperty{}, - } - c.SetVersion("2.0") - c.SetProductId("-//xevion//Banner Discord Bot//EN") - return c -} - // GetUser returns the user from the interaction. // This helper method is useful as depending on where the message was sent (guild or DM), the user is in a different field. func GetUser(interaction *discordgo.InteractionCreate) *discordgo.User { @@ -345,7 +334,7 @@ func GetUser(interaction *discordgo.InteractionCreate) *discordgo.User { // Encode encodes the values into “URL encoded” form // ("bar=baz&foo=quux") sorted by key. -func EncodeParams(params map[string][]string) string { +func EncodeParams(params map[string]*[]string) string { // Escape hatch for nil if params == nil { return "" @@ -364,7 +353,7 @@ func EncodeParams(params map[string][]string) string { values := params[k] keyEscaped := url.QueryEscape(k) - for _, v := range values { + for _, v := range *values { // If any parameters have been written, add the ampersand if buf.Len() > 0 { buf.WriteByte('&') @@ -379,3 +368,19 @@ func EncodeParams(params map[string][]string) string { return buf.String() } + +// TODO: Add a function to check if a term is view-only +func IsViewOnlyTerm(term string) bool { + return false +} + +// Point represents a point in 2D space +type Point struct { + X, Y float64 +} + +func Slope(p1 Point, p2 Point, x float64) Point { + slope := (p2.Y - p1.Y) / (p2.X - p1.X) + newY := slope*(x-p1.X) + p1.Y + return Point{X: x, Y: newY} +} diff --git a/scrape.go b/scrape.go index 9c52c9a..d350b15 100644 --- a/scrape.go +++ b/scrape.go @@ -144,16 +144,17 @@ func ScrapeMajor(subject string) error { } } + term := Default(time.Now()).ToString() + // Calculate the expiry time for the scrape (1 hour for every 200 classes, random +-15%) with a minimum of 1 hour var scrapeExpiry time.Duration if totalClassCount == 0 { scrapeExpiry = time.Hour * 12 } else { - scrapeExpiry = CalculateExpiry(totalClassCount, lo.Contains(PriorityMajors, subject)) + scrapeExpiry = CalculateExpiry(term, totalClassCount, lo.Contains(PriorityMajors, subject)) } // Mark the major as scraped - term := Default(time.Now()).ToString() if totalClassCount == 0 { totalClassCount = -1 } @@ -166,27 +167,45 @@ func ScrapeMajor(subject string) error { } // CalculateExpiry calculates the expiry time until the next scrape for a major. -func CalculateExpiry(count int, priority bool) time.Duration { - scrapeExpiry := time.Hour * time.Duration(count/100) - partial := scrapeExpiry.Seconds() * (rand.Float64() * 0.15) // Between 0 and 15% of the total +// term is the term for which the relevant course is occurring within. +// count is the number of courses that were scraped. +// priority is a boolean indicating whether the major is a priority major. +func CalculateExpiry(term string, count int, priority bool) time.Duration { + // Subjects with less than 50 classes have a reversed expiry (less classes, longer interval) + // 1 class => 12 hours, 49 classes => 1 hour + if count < 50 { + hours := Slope(Point{1, 12}, Point{49, 1}, float64(count)).Y + return time.Duration(hours * float64(time.Hour)) + } - // Randomly add or subtract the partial (delta between -15% and 15%) + // An hour for every 100 classes + baseExpiry := time.Hour * time.Duration(count/100) + + // If the subject is a priority, then the expiry is halved without variance + if priority { + return baseExpiry / 3 + } + + // If the term is considered "view only", then the expiry is multipled by 5 + var expiry = baseExpiry + if IsViewOnlyTerm(term) { + expiry *= 5 + } + + // Add minor variance to the expiry + expiryVariance := baseExpiry.Seconds() * (rand.Float64() * 0.15) // Between 0 and 15% of the total if rand.Intn(2) == 0 { - scrapeExpiry -= time.Duration(partial) * time.Second + expiry -= time.Duration(expiryVariance) * time.Second } else { - scrapeExpiry += time.Duration(partial) * time.Second + expiry += time.Duration(expiryVariance) * time.Second } // Ensure the expiry is at least 1 hour with up to 15 extra minutes - if scrapeExpiry < time.Hour { - scrapeExpiry = time.Hour + time.Duration(rand.Intn(60*15))*time.Second + if expiry < time.Hour { + baseExpiry = time.Hour + time.Duration(rand.Intn(60*15))*time.Second } - // If the subject is a priority, then the expiry is halved - if priority { - return scrapeExpiry / 3 - } - return scrapeExpiry + return baseExpiry } // IntakeCourse stores a course in Redis.