diff --git a/main.go b/main.go index 8a1fdae..9e9dcb8 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ package main import ( "flag" "github.com/velour/catbase/plugins/cli" + "github.com/velour/catbase/plugins/newsbid" "math/rand" "net/http" "os" @@ -124,6 +125,7 @@ func main() { b.AddPlugin(nerdepedia.New(b)) b.AddPlugin(tldr.New(b)) b.AddPlugin(stock.New(b)) + b.AddPlugin(newsbid.New(b)) b.AddPlugin(cli.New(b)) // catches anything left, will always return true b.AddPlugin(fact.New(b)) diff --git a/plugins/newsbid/newsbid.go b/plugins/newsbid/newsbid.go index c2355a1..d26dfd8 100644 --- a/plugins/newsbid/newsbid.go +++ b/plugins/newsbid/newsbid.go @@ -1,25 +1,109 @@ package newsbid import ( + "fmt" "github.com/jmoiron/sqlx" "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" + "github.com/velour/catbase/plugins/newsbid/webshit" + "sort" + "strconv" + "strings" ) type NewsBid struct { bot bot.Bot db *sqlx.DB + ws *webshit.Webshit } func New(b bot.Bot) *NewsBid { + ws := webshit.New(b.DB()) p := &NewsBid{ bot: b, db: b.DB(), + ws: ws, } p.bot.Register(p, bot.Message, p.message) return p } func (p *NewsBid) message(conn bot.Connector, k bot.Kind, message msg.Message, args ...interface{}) bool { + body := strings.ToLower(message.Body) + ch := message.Channel + if message.Command && body == "balance" { + bal := p.ws.GetBalance(message.User.Name) + p.bot.Send(conn, bot.Message, ch, fmt.Sprintf("%s, your current balance is %d.", + message.User.Name, bal)) + return true + } + if message.Command && body == "bids" { + bids, err := p.ws.GetAllBids() + if err != nil { + p.bot.Send(conn, bot.Message, ch, fmt.Sprintf("Error getting bids: %s", err)) + return true + } + if len(bids) == 0 { + p.bot.Send(conn, bot.Message, ch, "No bids to report.") + return true + } + sort.Slice(bids, func(i, j int) bool { return bids[i].User < bids[j].User }) + out := "Bids:\n" + for _, b := range bids { + out += fmt.Sprintf("%s bid %d on %s\n", b.User, b.Bid, b.Title) + } + p.bot.Send(conn, bot.Message, ch, out) + return true + } + if message.Command && body == "scores" { + bals, err := p.ws.GetAllBalances() + if err != nil { + p.bot.Send(conn, bot.Message, ch, fmt.Sprintf("Error getting bids: %s", err)) + return true + } + if len(bals) == 0 { + p.bot.Send(conn, bot.Message, ch, "No balances to report.") + return true + } + out := "NGate balances:\n" + for _, b := range bals { + out += fmt.Sprintf("%s has a total score of %d with %d left to bid this session\n", b.User, b.Score, b.Balance) + } + p.bot.Send(conn, bot.Message, ch, out) + return true + + } + if message.Command && strings.HasPrefix(body, "bid") { + parts := strings.Fields(body) + if len(parts) != 3 { + p.bot.Send(conn, bot.Message, ch, "You must bid with an amount and a URL.") + return true + } + amount, _ := strconv.Atoi(parts[1]) + url := parts[2] + if err := p.ws.Bid(message.User.Name, amount, url); err != nil { + p.bot.Send(conn, bot.Message, ch, fmt.Sprintf("Error placing bid: %s", err)) + } else { + p.bot.Send(conn, bot.Message, ch, "Your bid has been placed.") + } + return true + } + if message.Command && body == "check ngate" { + p.check(conn, ch) + return true + } return false } + +func (p *NewsBid) check(conn bot.Connector, ch string) { + wr, err := p.ws.Check() + if err != nil { + p.bot.Send(conn, bot.Message, ch, fmt.Sprintf("Error checking ngate: %s", err)) + return + } + for _, res := range wr { + msg := fmt.Sprintf("%s: won %d and lost %d for a score of %d", + res.User, res.Won, res.Lost, res.Score) + p.bot.Send(conn, bot.Message, ch, msg) + } +} diff --git a/plugins/newsbid/webshit/webshit.go b/plugins/newsbid/webshit/webshit.go index a558f3c..2d88bd5 100644 --- a/plugins/newsbid/webshit/webshit.go +++ b/plugins/newsbid/webshit/webshit.go @@ -24,17 +24,29 @@ type Story struct { } type Bid struct { - ID int - User string - Title string - URL string - Bid int + ID int + User string + Title string + URL string + Bid int + Placed int64 +} + +func (b Bid) PlacedParsed() time.Time { + return time.Unix(b.Placed, 0) +} + +type Balance struct { + User string + Balance int + Score int } type WeeklyResult struct { - User string - Won int - Lost int + User string + Won int + Lost int + Score int } func New(db *sqlx.DB) *Webshit { @@ -45,40 +57,36 @@ func New(db *sqlx.DB) *Webshit { // setup will create any necessary SQL tables and populate them with minimal data func (w *Webshit) setup() { - if _, err := w.db.Exec(`create table if not exists webshit_bids ( - id integer primary key, + w.db.MustExec(`create table if not exists webshit_bids ( + id integer primary key autoincrement, user string, title string, url string, bid integer, - created integer - )`); err != nil { - log.Fatal().Err(err) - } - if _, err := w.db.Exec(`create table if not exists webshit_balances ( + placed integer + )`) + w.db.MustExec(`create table if not exists webshit_balances ( user string primary key, balance int, score int - )`); err != nil { - log.Fatal().Err(err) - } + )`) } -func (w *Webshit) Check() (map[string]WeeklyResult, error) { +func (w *Webshit) Check() ([]WeeklyResult, error) { stories, published, err := w.GetWeekly() if err != nil { return nil, err } var bids []Bid - if err = w.db.Get(&bids, `select * from webshit_bids where created < ?`, + if err = w.db.Select(&bids, `select user,title,url,bid from webshit_bids where placed < ?`, published.Unix()); err != nil { return nil, err } // Assuming no bids earlier than the weekly means there hasn't been a new weekly if len(bids) == 0 { - return nil, nil + return nil, fmt.Errorf("there are no bids against the current ngate post") } storyMap := map[string]Story{} @@ -94,7 +102,7 @@ func (w *Webshit) Check() (map[string]WeeklyResult, error) { } // Delete all those bids - if _, err = w.db.Exec(`delete from webshit_bids where created < ?`, + if _, err = w.db.Exec(`delete from webshit_bids where placed < ?`, published.Unix()); err != nil { return nil, err } @@ -107,10 +115,11 @@ func (w *Webshit) Check() (map[string]WeeklyResult, error) { return wr, nil } -func (w *Webshit) checkBids(bids []Bid, storyMap map[string]Story) map[string]WeeklyResult { +func (w *Webshit) checkBids(bids []Bid, storyMap map[string]Story) []WeeklyResult { wr := map[string]WeeklyResult{} for _, b := range bids { win, loss := 0, 0 + score := w.GetScore(b.User) if s, ok := storyMap[b.Title]; ok { log.Info().Interface("story", s).Msg("won bid") win = b.Bid @@ -120,17 +129,19 @@ func (w *Webshit) checkBids(bids []Bid, storyMap map[string]Story) map[string]We } if res, ok := wr[b.User]; !ok { wr[b.User] = WeeklyResult{ - User: b.User, - Won: win, - Lost: loss, + User: b.User, + Won: win, + Lost: loss, + Score: score + win - loss, } } else { - res.Won = win - res.Lost = loss + res.Won += win + res.Lost += loss + res.Score += win - loss wr[b.User] = res } } - return wr + return wrMapToSlice(wr) } // GetHeadlines will return the current possible news headlines for bidding @@ -165,7 +176,7 @@ func (w *Webshit) GetWeekly() ([]Story, *time.Time, error) { return nil, nil, fmt.Errorf("no webshit weekly found") } - published := feed.PublishedParsed + published := feed.Items[0].PublishedParsed buf := bytes.NewBufferString(feed.Items[0].Description) doc, err := goquery.NewDocumentFromReader(buf) @@ -197,6 +208,34 @@ func (w *Webshit) GetBalance(user string) int { return balance } +func (w *Webshit) GetScore(user string) int { + q := `select score from webshit_balances where user=?` + var score int + err := w.db.Get(&score, q, user) + if err != nil { + return 0 + } + return score +} + +func (w *Webshit) GetAllBids() ([]Bid, error) { + var bids []Bid + err := w.db.Select(&bids, `select * from webshit_bids`) + if err != nil { + return nil, err + } + return bids, nil +} + +func (w *Webshit) GetAllBalances() ([]Balance, error) { + var balances []Balance + err := w.db.Select(&balances, `select * from webshit_balances`) + if err != nil { + return nil, err + } + return balances, nil +} + // Bid allows a user to place a bid on a particular story func (w *Webshit) Bid(user string, amount int, URL string) error { bal := w.GetBalance(user) @@ -209,14 +248,15 @@ func (w *Webshit) Bid(user string, amount int, URL string) error { } tx := w.db.MustBegin() - _, err = tx.Exec(`insert into webshit_bids (user,title,url,bid,created) values (?,?,?,?,?)`, + _, err = tx.Exec(`insert into webshit_bids (user,title,url,bid,placed) values (?,?,?,?,?)`, user, story.Title, story.URL, amount, time.Now().Unix()) if err != nil { tx.Rollback() return err } - _, err = tx.Exec(`update webshit_balances set balance=? where user=?`, - bal-amount, user) + q := `insert into webshit_balances (user,balance,score) values (?,?,0) + on conflict(user) do update set balance=?` + _, err = tx.Exec(q, user, bal-amount, bal-amount) if err != nil { tx.Rollback() return err @@ -259,11 +299,11 @@ func (w *Webshit) getStoryByURL(URL string) (Story, error) { }, nil } -func (w *Webshit) updateScores(results map[string]WeeklyResult) error { +func (w *Webshit) updateScores(results []WeeklyResult) error { tx := w.db.MustBegin() for _, res := range results { - if _, err := tx.Exec(`update webshit_balances set score=score+? where user=?`, - res.Won-res.Lost, res.User); err != nil { + if _, err := tx.Exec(`update webshit_balances set score=? where user=?`, + res.Score, res.User); err != nil { tx.Rollback() return err } @@ -271,3 +311,11 @@ func (w *Webshit) updateScores(results map[string]WeeklyResult) error { err := tx.Commit() return err } + +func wrMapToSlice(wr map[string]WeeklyResult) []WeeklyResult { + var out = []WeeklyResult{} + for _, r := range wr { + out = append(out, r) + } + return out +} diff --git a/plugins/newsbid/webshit/webshit_test.go b/plugins/newsbid/webshit/webshit_test.go index 7caddc9..39fbabc 100644 --- a/plugins/newsbid/webshit/webshit_test.go +++ b/plugins/newsbid/webshit/webshit_test.go @@ -17,7 +17,9 @@ func make(t *testing.T) *Webshit { func TestWebshit_GetWeekly(t *testing.T) { w := make(t) - weekly, _, err := w.GetWeekly() + weekly, pub, err := w.GetWeekly() + t.Logf("Pub: %v", pub) + assert.NotNil(t, pub) assert.Nil(t, err) assert.NotEmpty(t, weekly) }