Compare commits

..

8 Commits

10 changed files with 179 additions and 25 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
!assets/*.png !assets/*.png
.env .env
*.sqlite3 *.sqlite3
chessbot

13
Makefile Normal file
View File

@ -0,0 +1,13 @@
.PHONY: build test
SOURCES := main.go $(shell find chess -name '*.go')
build: chessbot
chessbot: $(SOURCES)
go build -o chessbot .
test:
## -count=1 is used to disable cache
## use -run <regexp> to only run tests that match <regexp>
go test ./chess -v -count=1

View File

@ -12,13 +12,19 @@ import (
"golang.org/x/image/font" "golang.org/x/image/font"
"golang.org/x/image/font/basicfont" "golang.org/x/image/font/basicfont"
"golang.org/x/image/font/opentype"
"golang.org/x/image/math/fixed" "golang.org/x/image/math/fixed"
) )
type Board struct { type Board struct {
tiles [8][8]*Piece tiles [8][8]*Piece
turn Color turn Color
Moves []string Moves []string
moveIndicators []Tile
}
type Tile struct {
X, Y int
} }
func NewBoard() *Board { func NewBoard() *Board {
@ -101,7 +107,7 @@ func (b *Board) Image() *image.RGBA {
x := xi * 128 x := xi * 128
y := yi * 128 y := yi * 128
rect = image.Rect(x, y, x+128, y+128) rect = image.Rect(x, y, x+128, y+128)
bg = image.NewUniform(getTileColor(xi, yi)) bg = image.NewUniform(getTileColor(b, xi, yi))
draw.Draw(img, rect, bg, p, draw.Src) draw.Draw(img, rect, bg, p, draw.Src)
piece = b.tiles[xi][yi] piece = b.tiles[xi][yi]
@ -186,17 +192,23 @@ func drawCoordinate(img *image.RGBA, x, y int, flipped bool) {
} }
drawString := func(s string, origin fixed.Point26_6) { drawString := func(s string, origin fixed.Point26_6) {
color := getTileColor(x, y) color := getTileColor(nil, x, y)
if !flipped && color == Light { if !flipped && color == Light {
color = Dark color = Dark
} else if !flipped { } else if !flipped {
color = Light color = Light
} }
// TODO: use SN font and make it bold
face, err := loadFontFace("lightningvolt.ttf")
if err != nil {
log.Printf("error loading font: %v\n", err)
face = basicfont.Face7x13
}
d := &font.Drawer{ d := &font.Drawer{
Dst: img, Dst: img,
Src: image.NewUniform(color), Src: image.NewUniform(color),
Face: basicfont.Face7x13, Face: face,
Dot: origin, Dot: origin,
} }
d.DrawString(s) d.DrawString(s)
@ -209,11 +221,36 @@ func drawCoordinate(img *image.RGBA, x, y int, flipped bool) {
} }
if row != "" { if row != "" {
origin = fixed.P((x+1)*128-12, y*128+15) origin = fixed.P((x+1)*128-20, y*128+23)
drawString(row, origin) drawString(row, origin)
} }
} }
func loadFontFace(path string) (font.Face, error) {
fontBytes, err := os.ReadFile(path)
if err != nil {
return nil, err
}
ttfFont, err := opentype.Parse(fontBytes)
if err != nil {
return nil, err
}
faceOptions := &opentype.FaceOptions{
Size: 32,
DPI: 72,
Hinting: font.HintingNone,
}
fontFace, err := opentype.NewFace(ttfFont, faceOptions)
if err != nil {
return nil, err
}
return fontFace, nil
}
func (b *Board) SetPiece(name PieceName, color Color, position string) error { func (b *Board) SetPiece(name PieceName, color Color, position string) error {
var ( var (
piece *Piece piece *Piece
@ -360,7 +397,7 @@ func (b *Board) Move(move string) error {
} }
// make sure the move is marked as a check if it was // make sure the move is marked as a check if it was
if b.InCheck() && !strings.HasSuffix(move, "+") { if b.InCheck() && !strings.HasSuffix(move, "+") && !strings.HasSuffix(move, "#") {
move += "+" move += "+"
} }
@ -379,6 +416,7 @@ func parseMove(move string) (string, int, int, string, error) {
) )
move = strings.TrimSuffix(move, "+") move = strings.TrimSuffix(move, "+")
move = strings.TrimSuffix(move, "#")
if move == "O-O" { if move == "O-O" {
return "K", 5, 7, "g1", nil return "K", 5, 7, "g1", nil
@ -801,6 +839,7 @@ func (b *Board) movePawn(position string, fromX int, fromY int, promotion string
if promotion != "" { if promotion != "" {
return b.promotePawn(toX, toY, promotion) return b.promotePawn(toX, toY, promotion)
} }
b.moveIndicators = []Tile{{fromX, fromY}, {toX, toY}}
return nil return nil
} }
@ -823,6 +862,7 @@ func (b *Board) movePawn(position string, fromX int, fromY int, promotion string
if promotion != "" { if promotion != "" {
return b.promotePawn(toX, toY, promotion) return b.promotePawn(toX, toY, promotion)
} }
b.moveIndicators = []Tile{{toX, yPrev}, {toX, toY}}
return nil return nil
} }
@ -839,6 +879,7 @@ func (b *Board) movePawn(position string, fromX int, fromY int, promotion string
if promotion != "" { if promotion != "" {
return b.promotePawn(toX, toY, promotion) return b.promotePawn(toX, toY, promotion)
} }
b.moveIndicators = []Tile{{toX, yPrev}, {toX, toY}}
return nil return nil
} }
@ -948,6 +989,7 @@ func (b *Board) moveRook(position string, queen bool, fromX int, fromY int) erro
p = b.getPiece(xPrev, yPrev) p = b.getPiece(xPrev, yPrev)
b.tiles[x][y] = p b.tiles[x][y] = p
b.tiles[xPrev][yPrev] = nil b.tiles[xPrev][yPrev] = nil
b.moveIndicators = []Tile{{xPrev, yPrev}, {x, y}}
return nil return nil
} }
@ -978,6 +1020,7 @@ func (b *Board) moveBishop(position string, queen bool) error {
if ((!queen && piece.Name == Bishop) || (queen && piece.Name == Queen)) && piece.Color == b.turn { if ((!queen && piece.Name == Bishop) || (queen && piece.Name == Queen)) && piece.Color == b.turn {
b.tiles[xPrev][yPrev] = nil b.tiles[xPrev][yPrev] = nil
b.tiles[x][y] = piece b.tiles[x][y] = piece
b.moveIndicators = []Tile{{xPrev, yPrev}, {x, y}}
return nil return nil
} else { } else {
// direction blocked by other piece // direction blocked by other piece
@ -996,6 +1039,7 @@ func (b *Board) moveBishop(position string, queen bool) error {
if ((!queen && piece.Name == Bishop) || (queen && piece.Name == Queen)) && piece.Color == b.turn { if ((!queen && piece.Name == Bishop) || (queen && piece.Name == Queen)) && piece.Color == b.turn {
b.tiles[xPrev][yPrev] = nil b.tiles[xPrev][yPrev] = nil
b.tiles[x][y] = piece b.tiles[x][y] = piece
b.moveIndicators = []Tile{{xPrev, yPrev}, {x, y}}
return nil return nil
} else { } else {
break break
@ -1013,6 +1057,7 @@ func (b *Board) moveBishop(position string, queen bool) error {
if ((!queen && piece.Name == Bishop) || (queen && piece.Name == Queen)) && piece.Color == b.turn { if ((!queen && piece.Name == Bishop) || (queen && piece.Name == Queen)) && piece.Color == b.turn {
b.tiles[xPrev][yPrev] = nil b.tiles[xPrev][yPrev] = nil
b.tiles[x][y] = piece b.tiles[x][y] = piece
b.moveIndicators = []Tile{{xPrev, yPrev}, {x, y}}
return nil return nil
} else { } else {
break break
@ -1030,6 +1075,7 @@ func (b *Board) moveBishop(position string, queen bool) error {
if ((!queen && piece.Name == Bishop) || (queen && piece.Name == Queen)) && piece.Color == b.turn { if ((!queen && piece.Name == Bishop) || (queen && piece.Name == Queen)) && piece.Color == b.turn {
b.tiles[xPrev][yPrev] = nil b.tiles[xPrev][yPrev] = nil
b.tiles[x][y] = piece b.tiles[x][y] = piece
b.moveIndicators = []Tile{{xPrev, yPrev}, {x, y}}
return nil return nil
} else { } else {
break break
@ -1123,6 +1169,7 @@ func (b *Board) moveKnight(position string, fromX int, fromY int) error {
p = b.getPiece(xPrev, yPrev) p = b.getPiece(xPrev, yPrev)
b.tiles[x][y] = p b.tiles[x][y] = p
b.tiles[xPrev][yPrev] = nil b.tiles[xPrev][yPrev] = nil
b.moveIndicators = []Tile{{xPrev, yPrev}, {x, y}}
return nil return nil
} }
@ -1194,6 +1241,8 @@ func (b *Board) moveKing(position string, castle bool) error {
b.tiles[5][y] = rook b.tiles[5][y] = rook
b.tiles[7][y] = nil b.tiles[7][y] = nil
b.moveIndicators = []Tile{{4, y}, {x, y}, {5, y}, {7, y}}
return nil return nil
} }
@ -1227,6 +1276,8 @@ func (b *Board) moveKing(position string, castle bool) error {
b.tiles[3][y] = rook b.tiles[3][y] = rook
b.tiles[0][y] = nil b.tiles[0][y] = nil
b.moveIndicators = []Tile{{2, y}, {4, y}, {3, y}, {0, y}}
return nil return nil
} }
} }
@ -1238,6 +1289,7 @@ func (b *Board) moveKing(position string, castle bool) error {
if piece != nil && piece.Name == King && piece.Color == b.turn { if piece != nil && piece.Name == King && piece.Color == b.turn {
b.tiles[xPrev][yPrev] = nil b.tiles[xPrev][yPrev] = nil
b.tiles[x][y] = piece b.tiles[x][y] = piece
b.moveIndicators = []Tile{{xPrev, yPrev}, {x, y}}
return nil return nil
} }
@ -1248,6 +1300,7 @@ func (b *Board) moveKing(position string, castle bool) error {
if piece != nil && piece.Name == King && piece.Color == b.turn { if piece != nil && piece.Name == King && piece.Color == b.turn {
b.tiles[xPrev][yPrev] = nil b.tiles[xPrev][yPrev] = nil
b.tiles[x][y] = piece b.tiles[x][y] = piece
b.moveIndicators = []Tile{{xPrev, yPrev}, {x, y}}
return nil return nil
} }
@ -1258,6 +1311,7 @@ func (b *Board) moveKing(position string, castle bool) error {
if piece != nil && piece.Name == King && piece.Color == b.turn { if piece != nil && piece.Name == King && piece.Color == b.turn {
b.tiles[xPrev][yPrev] = nil b.tiles[xPrev][yPrev] = nil
b.tiles[x][y] = piece b.tiles[x][y] = piece
b.moveIndicators = []Tile{{xPrev, yPrev}, {x, y}}
return nil return nil
} }
@ -1268,6 +1322,7 @@ func (b *Board) moveKing(position string, castle bool) error {
if piece != nil && piece.Name == King && piece.Color == b.turn { if piece != nil && piece.Name == King && piece.Color == b.turn {
b.tiles[xPrev][yPrev] = nil b.tiles[xPrev][yPrev] = nil
b.tiles[x][y] = piece b.tiles[x][y] = piece
b.moveIndicators = []Tile{{xPrev, yPrev}, {x, y}}
return nil return nil
} }
@ -1278,6 +1333,7 @@ func (b *Board) moveKing(position string, castle bool) error {
if piece != nil && piece.Name == King && piece.Color == b.turn { if piece != nil && piece.Name == King && piece.Color == b.turn {
b.tiles[xPrev][yPrev] = nil b.tiles[xPrev][yPrev] = nil
b.tiles[x][y] = piece b.tiles[x][y] = piece
b.moveIndicators = []Tile{{xPrev, yPrev}, {x, y}}
return nil return nil
} }
@ -1288,6 +1344,7 @@ func (b *Board) moveKing(position string, castle bool) error {
if piece != nil && piece.Name == King && piece.Color == b.turn { if piece != nil && piece.Name == King && piece.Color == b.turn {
b.tiles[xPrev][yPrev] = nil b.tiles[xPrev][yPrev] = nil
b.tiles[x][y] = piece b.tiles[x][y] = piece
b.moveIndicators = []Tile{{xPrev, yPrev}, {x, y}}
return nil return nil
} }
@ -1298,6 +1355,7 @@ func (b *Board) moveKing(position string, castle bool) error {
if piece != nil && piece.Name == King && piece.Color == b.turn { if piece != nil && piece.Name == King && piece.Color == b.turn {
b.tiles[xPrev][yPrev] = nil b.tiles[xPrev][yPrev] = nil
b.tiles[x][y] = piece b.tiles[x][y] = piece
b.moveIndicators = []Tile{{xPrev, yPrev}, {x, y}}
return nil return nil
} }
@ -1308,6 +1366,7 @@ func (b *Board) moveKing(position string, castle bool) error {
if piece != nil && piece.Name == King && piece.Color == b.turn { if piece != nil && piece.Name == King && piece.Color == b.turn {
b.tiles[xPrev][yPrev] = nil b.tiles[xPrev][yPrev] = nil
b.tiles[x][y] = piece b.tiles[x][y] = piece
b.moveIndicators = []Tile{{xPrev, yPrev}, {x, y}}
return nil return nil
} }
@ -1392,8 +1451,25 @@ func (b *Board) getCollision(position string) (*Piece, error) {
return nil, nil return nil, nil
} }
func getTileColor(x, y int) Color { func getTileColor(b *Board, x, y int) Color {
if x%2 == y%2 {
lightTile := x%2 == y%2
if b != nil {
// highlight move
// TODO: refactor using alpha channels
for _, t := range b.moveIndicators {
if t.X == x && t.Y == y {
if lightTile {
return LightGreen
} else {
return DarkGreen
}
}
}
}
if lightTile {
return Light return Light
} else { } else {
return Dark return Dark

View File

@ -472,6 +472,22 @@ func TestBoardCheck(t *testing.T) {
assertParse(t, b, "Kxf7") assertParse(t, b, "Kxf7")
} }
func TestBoardCheckmate(t *testing.T) {
t.Parallel()
b := chess.NewBoard()
assert.False(t, b.InCheck())
// fool's mate
assertParse(t, b, "f3 e6 g4 Qh4#")
assert.True(t, b.InCheck())
assert.True(t, strings.HasSuffix(b.Moves[len(b.Moves)-1], "#"), "checkmate move should end with #")
assertMoveError(t, b, "a3", "invalid move a3: king is in check")
}
func TestBoardPin(t *testing.T) { func TestBoardPin(t *testing.T) {
t.Parallel() t.Parallel()

View File

@ -58,8 +58,10 @@ const (
type Color color.Color type Color color.Color
var ( var (
Light Color = color.RGBA{240, 217, 181, 255} Light Color = color.RGBA{240, 217, 181, 255}
Dark Color = color.RGBA{181, 136, 99, 255} Dark Color = color.RGBA{181, 136, 99, 255}
LightGreen Color = color.RGBA{205, 210, 106, 255}
DarkGreen Color = color.RGBA{170, 162, 58, 255}
) )
func NewPiece(name PieceName, color Color) (*Piece, error) { func NewPiece(name PieceName, color Color) (*Piece, error) {

3
go.mod
View File

@ -3,7 +3,7 @@ module github.com/ekzyis/chessbot
go 1.23.0 go 1.23.0
require ( require (
github.com/ekzyis/snappy v0.6.2 github.com/ekzyis/snappy v0.7.0
github.com/mattn/go-sqlite3 v1.14.23 github.com/mattn/go-sqlite3 v1.14.23
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
golang.org/x/image v0.20.0 golang.org/x/image v0.20.0
@ -12,6 +12,7 @@ require (
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/text v0.18.0 // indirect
gopkg.in/guregu/null.v4 v4.0.0 // indirect gopkg.in/guregu/null.v4 v4.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

6
go.sum
View File

@ -1,7 +1,7 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ekzyis/snappy v0.6.2 h1:iOPgoS0cSUNk8leQJku0bBgnsha/+DcYUwTYqGLINY4= github.com/ekzyis/snappy v0.7.0 h1:RcFTUHdZFTBFOnh6cG9HCL/RPK6L13qdxDrjBNZFjEM=
github.com/ekzyis/snappy v0.6.2/go.mod h1:UksYI0dU0+cnzz0LQjWB1P0QQP/ghx47e4atP99a5Lk= github.com/ekzyis/snappy v0.7.0/go.mod h1:UksYI0dU0+cnzz0LQjWB1P0QQP/ghx47e4atP99a5Lk=
github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0= github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0=
github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -10,6 +10,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw= golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=
golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM= golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/guregu/null.v4 v4.0.0 h1:1Wm3S1WEA2I26Kq+6vcW+w0gcDo44YKYD7YIEJNHDjg= gopkg.in/guregu/null.v4 v4.0.0 h1:1Wm3S1WEA2I26Kq+6vcW+w0gcDo44YKYD7YIEJNHDjg=

BIN
lightningvolt.ttf Normal file

Binary file not shown.

60
main.go
View File

@ -14,21 +14,54 @@ import (
) )
var ( var (
c = sn.GetClient() c = sn.GetClient()
// TODO: fetch our id from SN API me *sn.User
// prod: 25176 | local: 21858
meId = 25176
) )
func main() { func main() {
for { for {
updateMe()
tickGameStart(c) tickGameStart(c)
tickGameProgress(c) tickGameProgress(c)
time.Sleep(15 * time.Second) time.Sleep(15 * time.Second)
} }
} }
func updateMe() {
var (
oldMe *sn.User
err error
warnThreshold = 100
)
maybeWarn := func() {
if me.Privates.Sats < warnThreshold {
log.Printf("~~~ warning: low balance ~~~\n")
}
}
if me == nil {
// make sure first update is successful
if me, err = c.Me(); err != nil {
log.Fatalf("failed to fetch me: %v\n", err)
}
log.Printf("fetched me: id=%d name=%s balance=%d\n", me.Id, me.Name, me.Privates.Sats)
maybeWarn()
return
}
oldMe = me
if me, err = c.Me(); err != nil {
log.Printf("failed to update me: %v\n", err)
me = oldMe
} else {
log.Printf("updated me. balance: %d\n", me.Privates.Sats)
}
maybeWarn()
}
func tickGameStart(c *sn.Client) { func tickGameStart(c *sn.Client) {
var ( var (
mentions []sn.Notification mentions []sn.Notification
@ -98,7 +131,7 @@ func tickGameProgress(c *sn.Client) {
if parent, err := c.Item(n.Item.ParentId); err != nil { if parent, err := c.Item(n.Item.ParentId); err != nil {
log.Printf("failed to fetch parent %d of %d\n", n.Item.ParentId, n.Item.Id) log.Printf("failed to fetch parent %d of %d\n", n.Item.ParentId, n.Item.Id)
continue continue
} else if parent.User.Id != meId { } else if parent.User.Id != me.Id {
log.Printf("ignoring nested reply %d\n", n.Item.Id) log.Printf("ignoring nested reply %d\n", n.Item.Id)
continue continue
} }
@ -146,8 +179,15 @@ func handleGameStart(req *sn.Item) error {
return fmt.Errorf("failed to upload image for item %d: %v\n", req.Id, err) return fmt.Errorf("failed to upload image for item %d: %v\n", req.Id, err)
} }
// reply with algebraic notation and image // reply with algebraic notation, image and info
res = strings.Trim(fmt.Sprintf("%s\n\n%s", b.AlgebraicNotation(), imgUrl), " ") infoMove := "e4"
if len(b.Moves) > 0 {
infoMove = "e5"
}
info := fmt.Sprintf("_A new chess game has been started!_\n\n"+
"_Reply with a move like `%s` to continue the game. "+
"See [here](https://stacker.news/chess#how-to-continue) for details._", infoMove)
res = strings.Trim(fmt.Sprintf("%s\n\n%s\n\n%s", b.AlgebraicNotation(), imgUrl, info), " ")
if _, err = createComment(req.Id, res); err != nil { if _, err = createComment(req.Id, res); err != nil {
return fmt.Errorf("failed to reply to item %d: %v\n", req.Id, err) return fmt.Errorf("failed to reply to item %d: %v\n", req.Id, err)
} }
@ -176,7 +216,7 @@ func handleGameProgress(req *sn.Item) error {
} }
for _, item := range thread { for _, item := range thread {
if item.User.Id == meId { if item.User.Id == me.Id {
continue continue
} }
@ -278,6 +318,8 @@ func parseGameStart(input string) (string, error) {
} }
func parseGameProgress(input string) (string, error) { func parseGameProgress(input string) (string, error) {
input = strings.Trim(input, " ")
lines := strings.Split(input, "\n") lines := strings.Split(input, "\n")
words := strings.Split(input, " ") words := strings.Split(input, " ")
@ -305,5 +347,5 @@ func isRecent(t time.Time) bool {
} }
func alreadyHandled(id int) (bool, error) { func alreadyHandled(id int) (bool, error) {
return db.ItemHasReply(id, meId) return db.ItemHasReply(id, me.Id)
} }

View File

@ -13,6 +13,7 @@ import (
type Client = snappy.Client type Client = snappy.Client
type Notification = snappy.Notification type Notification = snappy.Notification
type Item = snappy.Item type Item = snappy.Item
type User = snappy.User
var ( var (
c *Client c *Client