bid: connect scores/bids/etc to the chat

This commit is contained in:
Chris Sexton 2019-07-15 16:55:35 -04:00
parent dd0f9efeae
commit 286582417b
4 changed files with 173 additions and 37 deletions

View File

@ -5,6 +5,7 @@ package main
import ( import (
"flag" "flag"
"github.com/velour/catbase/plugins/cli" "github.com/velour/catbase/plugins/cli"
"github.com/velour/catbase/plugins/newsbid"
"math/rand" "math/rand"
"net/http" "net/http"
"os" "os"
@ -124,6 +125,7 @@ func main() {
b.AddPlugin(nerdepedia.New(b)) b.AddPlugin(nerdepedia.New(b))
b.AddPlugin(tldr.New(b)) b.AddPlugin(tldr.New(b))
b.AddPlugin(stock.New(b)) b.AddPlugin(stock.New(b))
b.AddPlugin(newsbid.New(b))
b.AddPlugin(cli.New(b)) b.AddPlugin(cli.New(b))
// catches anything left, will always return true // catches anything left, will always return true
b.AddPlugin(fact.New(b)) b.AddPlugin(fact.New(b))

View File

@ -1,25 +1,109 @@
package newsbid package newsbid
import ( import (
"fmt"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"github.com/velour/catbase/bot" "github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg" "github.com/velour/catbase/bot/msg"
"github.com/velour/catbase/plugins/newsbid/webshit"
"sort"
"strconv"
"strings"
) )
type NewsBid struct { type NewsBid struct {
bot bot.Bot bot bot.Bot
db *sqlx.DB db *sqlx.DB
ws *webshit.Webshit
} }
func New(b bot.Bot) *NewsBid { func New(b bot.Bot) *NewsBid {
ws := webshit.New(b.DB())
p := &NewsBid{ p := &NewsBid{
bot: b, bot: b,
db: b.DB(), db: b.DB(),
ws: ws,
} }
p.bot.Register(p, bot.Message, p.message) p.bot.Register(p, bot.Message, p.message)
return p return p
} }
func (p *NewsBid) message(conn bot.Connector, k bot.Kind, message msg.Message, args ...interface{}) bool { 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 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)
}
}

View File

@ -24,17 +24,29 @@ type Story struct {
} }
type Bid struct { type Bid struct {
ID int ID int
User string User string
Title string Title string
URL string URL string
Bid int 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 { type WeeklyResult struct {
User string User string
Won int Won int
Lost int Lost int
Score int
} }
func New(db *sqlx.DB) *Webshit { 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 // setup will create any necessary SQL tables and populate them with minimal data
func (w *Webshit) setup() { func (w *Webshit) setup() {
if _, err := w.db.Exec(`create table if not exists webshit_bids ( w.db.MustExec(`create table if not exists webshit_bids (
id integer primary key, id integer primary key autoincrement,
user string, user string,
title string, title string,
url string, url string,
bid integer, bid integer,
created integer placed integer
)`); err != nil { )`)
log.Fatal().Err(err) w.db.MustExec(`create table if not exists webshit_balances (
}
if _, err := w.db.Exec(`create table if not exists webshit_balances (
user string primary key, user string primary key,
balance int, balance int,
score 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() stories, published, err := w.GetWeekly()
if err != nil { if err != nil {
return nil, err return nil, err
} }
var bids []Bid 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 { published.Unix()); err != nil {
return nil, err return nil, err
} }
// Assuming no bids earlier than the weekly means there hasn't been a new weekly // Assuming no bids earlier than the weekly means there hasn't been a new weekly
if len(bids) == 0 { if len(bids) == 0 {
return nil, nil return nil, fmt.Errorf("there are no bids against the current ngate post")
} }
storyMap := map[string]Story{} storyMap := map[string]Story{}
@ -94,7 +102,7 @@ func (w *Webshit) Check() (map[string]WeeklyResult, error) {
} }
// Delete all those bids // 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 { published.Unix()); err != nil {
return nil, err return nil, err
} }
@ -107,10 +115,11 @@ func (w *Webshit) Check() (map[string]WeeklyResult, error) {
return wr, nil 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{} wr := map[string]WeeklyResult{}
for _, b := range bids { for _, b := range bids {
win, loss := 0, 0 win, loss := 0, 0
score := w.GetScore(b.User)
if s, ok := storyMap[b.Title]; ok { if s, ok := storyMap[b.Title]; ok {
log.Info().Interface("story", s).Msg("won bid") log.Info().Interface("story", s).Msg("won bid")
win = b.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 { if res, ok := wr[b.User]; !ok {
wr[b.User] = WeeklyResult{ wr[b.User] = WeeklyResult{
User: b.User, User: b.User,
Won: win, Won: win,
Lost: loss, Lost: loss,
Score: score + win - loss,
} }
} else { } else {
res.Won = win res.Won += win
res.Lost = loss res.Lost += loss
res.Score += win - loss
wr[b.User] = res wr[b.User] = res
} }
} }
return wr return wrMapToSlice(wr)
} }
// GetHeadlines will return the current possible news headlines for bidding // 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") return nil, nil, fmt.Errorf("no webshit weekly found")
} }
published := feed.PublishedParsed published := feed.Items[0].PublishedParsed
buf := bytes.NewBufferString(feed.Items[0].Description) buf := bytes.NewBufferString(feed.Items[0].Description)
doc, err := goquery.NewDocumentFromReader(buf) doc, err := goquery.NewDocumentFromReader(buf)
@ -197,6 +208,34 @@ func (w *Webshit) GetBalance(user string) int {
return balance 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 // Bid allows a user to place a bid on a particular story
func (w *Webshit) Bid(user string, amount int, URL string) error { func (w *Webshit) Bid(user string, amount int, URL string) error {
bal := w.GetBalance(user) bal := w.GetBalance(user)
@ -209,14 +248,15 @@ func (w *Webshit) Bid(user string, amount int, URL string) error {
} }
tx := w.db.MustBegin() 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()) user, story.Title, story.URL, amount, time.Now().Unix())
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
return err return err
} }
_, err = tx.Exec(`update webshit_balances set balance=? where user=?`, q := `insert into webshit_balances (user,balance,score) values (?,?,0)
bal-amount, user) on conflict(user) do update set balance=?`
_, err = tx.Exec(q, user, bal-amount, bal-amount)
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
return err return err
@ -259,11 +299,11 @@ func (w *Webshit) getStoryByURL(URL string) (Story, error) {
}, nil }, nil
} }
func (w *Webshit) updateScores(results map[string]WeeklyResult) error { func (w *Webshit) updateScores(results []WeeklyResult) error {
tx := w.db.MustBegin() tx := w.db.MustBegin()
for _, res := range results { for _, res := range results {
if _, err := tx.Exec(`update webshit_balances set score=score+? where user=?`, if _, err := tx.Exec(`update webshit_balances set score=? where user=?`,
res.Won-res.Lost, res.User); err != nil { res.Score, res.User); err != nil {
tx.Rollback() tx.Rollback()
return err return err
} }
@ -271,3 +311,11 @@ func (w *Webshit) updateScores(results map[string]WeeklyResult) error {
err := tx.Commit() err := tx.Commit()
return err return err
} }
func wrMapToSlice(wr map[string]WeeklyResult) []WeeklyResult {
var out = []WeeklyResult{}
for _, r := range wr {
out = append(out, r)
}
return out
}

View File

@ -17,7 +17,9 @@ func make(t *testing.T) *Webshit {
func TestWebshit_GetWeekly(t *testing.T) { func TestWebshit_GetWeekly(t *testing.T) {
w := make(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.Nil(t, err)
assert.NotEmpty(t, weekly) assert.NotEmpty(t, weekly)
} }