mirror of
https://github.com/Eggbertx/gochan.git
synced 2025-09-03 09:26:23 -07:00
Implement dice rolling
This commit is contained in:
parent
d5ac9bff11
commit
e65d363a84
7 changed files with 174 additions and 30 deletions
|
@ -6,6 +6,7 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gochan-org/gochan/pkg/building"
|
||||
"github.com/gochan-org/gochan/pkg/config"
|
||||
|
@ -29,16 +30,16 @@ func editPost(checkedPosts []int, editBtn string, doEdit string, writer http.Res
|
|||
if editBtn == "Edit post" {
|
||||
var err error
|
||||
if len(checkedPosts) == 0 {
|
||||
server.ServeErrorPage(writer, "You need to select one post to edit.")
|
||||
server.ServeError(writer, server.NewServerError("You need to select one post to edit", http.StatusBadRequest), wantsJSON, nil)
|
||||
return
|
||||
} else if len(checkedPosts) > 1 {
|
||||
server.ServeErrorPage(writer, "You can only edit one post at a time.")
|
||||
server.ServeError(writer, server.NewServerError("You can only edit one post at a time", http.StatusBadRequest), wantsJSON, nil)
|
||||
return
|
||||
}
|
||||
|
||||
rank := manage.GetStaffRank(request)
|
||||
if password == "" && rank == 0 {
|
||||
server.ServeErrorPage(writer, "Password required for post editing")
|
||||
server.ServeError(writer, server.NewServerError("Password required for post editing", http.StatusUnauthorized), wantsJSON, nil)
|
||||
return
|
||||
}
|
||||
passwordMD5 := gcutil.Md5Sum(password)
|
||||
|
@ -47,26 +48,32 @@ func editPost(checkedPosts []int, editBtn string, doEdit string, writer http.Res
|
|||
if err != nil {
|
||||
errEv.Err(err).Caller().
|
||||
Msg("Error getting post information")
|
||||
server.ServeError(writer, server.NewServerError("Error getting post information", http.StatusInternalServerError), wantsJSON, nil)
|
||||
return
|
||||
}
|
||||
errEv.Int("postID", post.ID)
|
||||
|
||||
if post.Password != passwordMD5 && rank == 0 {
|
||||
server.ServeErrorPage(writer, "Wrong password")
|
||||
server.ServeError(writer, server.NewServerError("Wrong password", http.StatusUnauthorized), wantsJSON, nil)
|
||||
return
|
||||
}
|
||||
|
||||
board, err := post.GetBoard()
|
||||
if err != nil {
|
||||
errEv.Err(err).Caller().Msg("Unable to get board ID from post")
|
||||
server.ServeErrorPage(writer, "Unable to get board ID from post: "+err.Error())
|
||||
server.ServeError(writer, server.NewServerError("Unable to get board ID from post", http.StatusInternalServerError), wantsJSON, nil)
|
||||
return
|
||||
}
|
||||
if strings.Contains(string(post.Message), `<span class="dice-roll">`) && !config.GetBoardConfig(board.Dir).AllowDiceRerolls {
|
||||
server.ServeError(writer, server.NewServerError("Dice rerolls are not allowed on this board", http.StatusBadRequest), wantsJSON, nil)
|
||||
return
|
||||
}
|
||||
|
||||
errEv.Str("board", board.Dir)
|
||||
upload, err := post.GetUpload()
|
||||
if err != nil {
|
||||
errEv.Err(err).Caller().Send()
|
||||
server.ServeErrorPage(writer, "Error getting post upload info: "+err.Error())
|
||||
server.ServeError(writer, server.NewServerError("Error getting post upload info: "+err.Error(), http.StatusInternalServerError), wantsJSON, nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -87,7 +94,7 @@ func editPost(checkedPosts []int, editBtn string, doEdit string, writer http.Res
|
|||
if err = serverutil.MinifyTemplate(gctemplates.PostEdit, data, &buf, "text/html"); err != nil {
|
||||
errEv.Err(err).Caller().
|
||||
Msg("Error executing edit post template")
|
||||
server.ServeError(writer, "Error executing edit post template: "+err.Error(), wantsJSON, nil)
|
||||
server.ServeError(writer, server.NewServerError("Error executing edit post template: "+err.Error(), http.StatusInternalServerError), wantsJSON, nil)
|
||||
return
|
||||
}
|
||||
writer.Write(buf.Bytes())
|
||||
|
@ -101,7 +108,7 @@ func editPost(checkedPosts []int, editBtn string, doEdit string, writer http.Res
|
|||
errEv.Err(err).Caller().
|
||||
Str("postid", postIDstr).
|
||||
Msg("Invalid form data")
|
||||
server.ServeError(writer, "Invalid form data: "+err.Error(), wantsJSON, map[string]any{
|
||||
server.ServeError(writer, server.NewServerError("Invalid form data: "+err.Error(), http.StatusBadRequest), wantsJSON, map[string]any{
|
||||
"postid": postid,
|
||||
})
|
||||
return
|
||||
|
@ -110,7 +117,7 @@ func editPost(checkedPosts []int, editBtn string, doEdit string, writer http.Res
|
|||
post, err := gcsql.GetPostFromID(postid, true)
|
||||
if err != nil {
|
||||
errEv.Err(err).Caller().Msg("Unable to find post")
|
||||
server.ServeError(writer, "Unable to find post", wantsJSON, map[string]any{
|
||||
server.ServeError(writer, server.NewServerError("Unable to find post", http.StatusBadRequest), wantsJSON, map[string]any{
|
||||
"postid": postid,
|
||||
})
|
||||
return
|
||||
|
@ -121,7 +128,7 @@ func editPost(checkedPosts []int, editBtn string, doEdit string, writer http.Res
|
|||
errEv.Err(err).Caller().
|
||||
Str("boardID", boardIDstr).
|
||||
Msg("Invalid form data")
|
||||
server.ServeError(writer, "Invalid form data: "+err.Error(), wantsJSON, nil)
|
||||
server.ServeError(writer, server.NewServerError("Invalid form data: "+err.Error(), http.StatusBadRequest), wantsJSON, nil)
|
||||
return
|
||||
}
|
||||
gcutil.LogInt("boardID", boardid, infoEv, errEv)
|
||||
|
@ -129,13 +136,13 @@ func editPost(checkedPosts []int, editBtn string, doEdit string, writer http.Res
|
|||
rank := manage.GetStaffRank(request)
|
||||
passwordMD5 := gcutil.Md5Sum(password)
|
||||
if post.Password != passwordMD5 && rank == 0 {
|
||||
server.ServeError(writer, "Wrong password", wantsJSON, nil)
|
||||
server.ServeError(writer, server.NewServerError("Wrong password", http.StatusUnauthorized), wantsJSON, nil)
|
||||
return
|
||||
}
|
||||
|
||||
board, err := gcsql.GetBoardFromID(boardid)
|
||||
if err != nil {
|
||||
server.ServeError(writer, "Invalid form data: "+err.Error(), wantsJSON, map[string]any{
|
||||
server.ServeError(writer, server.NewServerError("Invalid form data: "+err.Error(), http.StatusBadRequest), wantsJSON, map[string]any{
|
||||
"boardid": boardid,
|
||||
})
|
||||
errEv.Err(err).Caller().Msg("Invalid form data")
|
||||
|
@ -145,18 +152,18 @@ func editPost(checkedPosts []int, editBtn string, doEdit string, writer http.Res
|
|||
if doEdit == "upload" {
|
||||
oldUpload, err := post.GetUpload()
|
||||
if err != nil {
|
||||
errEv.Err(err).Caller().Send()
|
||||
server.ServeError(writer, err.Error(), wantsJSON, nil)
|
||||
errEv.Err(err).Caller().Msg("Unable to get post upload")
|
||||
server.ServeError(writer, server.NewServerError("Error getting post upload info: "+err.Error(), http.StatusInternalServerError), wantsJSON, nil)
|
||||
return
|
||||
}
|
||||
|
||||
upload, err := uploads.AttachUploadFromRequest(request, writer, post, board, gcutil.LogInfo(), errEv)
|
||||
if err != nil {
|
||||
server.ServeError(writer, err.Error(), wantsJSON, nil)
|
||||
server.ServeError(writer, server.NewServerError("Unable to attach upload:"+err.Error(), http.StatusInternalServerError), wantsJSON, nil)
|
||||
return
|
||||
}
|
||||
if upload == nil {
|
||||
server.ServeError(writer, "Missing upload replacement", wantsJSON, nil)
|
||||
server.ServeError(writer, server.NewServerError("Missing upload replacement", http.StatusBadRequest), wantsJSON, nil)
|
||||
return
|
||||
}
|
||||
documentRoot := config.GetSystemCriticalConfig().DocumentRoot
|
||||
|
@ -167,7 +174,7 @@ func editPost(checkedPosts []int, editBtn string, doEdit string, writer http.Res
|
|||
path.Join(documentRoot, board.Dir, "thumb", oldUpload.Filename))
|
||||
if err = post.UnlinkUploads(false); err != nil {
|
||||
errEv.Err(err).Caller().Send()
|
||||
server.ServeError(writer, "Error unlinking old upload from post: "+err.Error(), wantsJSON, nil)
|
||||
server.ServeError(writer, server.NewServerError("Error unlinking old upload from post: "+err.Error(), http.StatusInternalServerError), wantsJSON, nil)
|
||||
return
|
||||
}
|
||||
if oldUpload.Filename != "deleted" {
|
||||
|
@ -184,7 +191,7 @@ func editPost(checkedPosts []int, editBtn string, doEdit string, writer http.Res
|
|||
Str("newFilename", upload.Filename).
|
||||
Str("newOriginalFilename", upload.OriginalFilename).
|
||||
Send()
|
||||
server.ServeError(writer, "Error attaching new upload: "+err.Error(), wantsJSON, map[string]any{
|
||||
server.ServeError(writer, server.NewServerError("Error attaching new upload: "+err.Error(), http.StatusInternalServerError), wantsJSON, map[string]any{
|
||||
"filename": upload.OriginalFilename,
|
||||
})
|
||||
filePath = path.Join(documentRoot, board.Dir, "src", upload.Filename)
|
||||
|
@ -200,17 +207,15 @@ func editPost(checkedPosts []int, editBtn string, doEdit string, writer http.Res
|
|||
var recovered bool
|
||||
_, err, recovered = events.TriggerEvent("message-pre-format", post, request)
|
||||
if recovered {
|
||||
writer.WriteHeader(http.StatusInternalServerError)
|
||||
server.ServeError(writer, "Recovered from a panic in an event handler (message-pre-format)", wantsJSON, map[string]any{
|
||||
"postid": post.ID,
|
||||
})
|
||||
server.ServeError(writer, server.NewServerError("Recovered from a panic in an event handler (message-pre-format)",
|
||||
http.StatusInternalServerError), wantsJSON, map[string]any{"postid": post.ID})
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
errEv.Err(err).Caller().
|
||||
Str("triggeredEvent", "message-pre-format").
|
||||
Send()
|
||||
server.ServeError(writer, err.Error(), wantsJSON, map[string]any{
|
||||
server.ServeError(writer, server.NewServerError(err.Error(), http.StatusInternalServerError), wantsJSON, map[string]any{
|
||||
"postid": post.ID,
|
||||
})
|
||||
return
|
||||
|
@ -219,8 +224,7 @@ func editPost(checkedPosts []int, editBtn string, doEdit string, writer http.Res
|
|||
// trigger the pre-format event
|
||||
_, err, recovered := events.TriggerEvent("message-pre-format", post, request)
|
||||
if recovered {
|
||||
writer.WriteHeader(http.StatusInternalServerError)
|
||||
server.ServeError(writer, "Recovered from a panic in an event handler (message-pre-format)", wantsJSON, nil)
|
||||
server.ServeError(writer, server.NewServerError("Recovered from a panic in an event handler (message-pre-format)", http.StatusInternalServerError), wantsJSON, nil)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
|
@ -285,9 +289,9 @@ func editPost(checkedPosts []int, editBtn string, doEdit string, writer http.Res
|
|||
}
|
||||
|
||||
if err = building.BuildBoards(false, boardid); err != nil {
|
||||
server.ServeErrorPage(writer, "Error rebuilding boards: "+err.Error())
|
||||
server.ServeError(writer, "Error rebuilding boards: "+err.Error(), wantsJSON, nil)
|
||||
} else if err = building.BuildFrontPage(); err != nil {
|
||||
server.ServeErrorPage(writer, "Error rebuilding front page: "+err.Error())
|
||||
server.ServeError(writer, "Error rebuilding front page: "+err.Error(), wantsJSON, nil)
|
||||
} else {
|
||||
http.Redirect(writer, request, post.WebPath(), http.StatusFound)
|
||||
infoEv.Msg("Post edited")
|
||||
|
|
|
@ -100,6 +100,8 @@
|
|||
"EmbedHeight": 164,
|
||||
"ImagesOpenNewTab": true,
|
||||
"NewTabOnOutlinks": true,
|
||||
"DisableBBcode": false,
|
||||
"AllowDiceRerolls": false,
|
||||
|
||||
"MinifyHTML": true,
|
||||
"MinifyJS": true,
|
||||
|
@ -111,8 +113,8 @@
|
|||
"SiteKey": "your site key goes here (if you want a captcha, make sure to replace '_Captcha' with 'Captcha'",
|
||||
"AccountSecret": "your account secret key goes here"
|
||||
},
|
||||
"GeoIPType": "mmdb",
|
||||
"GeoIPOptions": {
|
||||
"_GeoIPType": "mmdb",
|
||||
"_GeoIPOptions": {
|
||||
"dbLocation": "/usr/share/geoip/GeoIP2.mmdb",
|
||||
"isoCode": "en"
|
||||
},
|
||||
|
|
|
@ -332,6 +332,7 @@ type PostConfig struct {
|
|||
ImagesOpenNewTab bool
|
||||
NewTabOnOutlinks bool
|
||||
DisableBBcode bool
|
||||
AllowDiceRerolls bool
|
||||
}
|
||||
|
||||
func WriteConfig() error {
|
||||
|
|
|
@ -3,6 +3,7 @@ package posting
|
|||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"math/rand"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -17,6 +18,7 @@ var (
|
|||
msgfmtr MessageFormatter
|
||||
urlRE = regexp.MustCompile(`https?://(\S+)`)
|
||||
unsetBBcodeTags = []string{"center", "color", "img", "quote", "size"}
|
||||
diceRoller = regexp.MustCompile(`(?i)\[(\d*)d(\d+)(?:([+-])(\d+))?\]`)
|
||||
)
|
||||
|
||||
// InitPosting prepares the formatter and the temp post pruner
|
||||
|
@ -131,3 +133,51 @@ func FormatMessage(message string, boardDir string) (template.HTML, error) {
|
|||
}
|
||||
return template.HTML(strings.Join(postLines, "<br />")), nil // skipcq: GSC-G203
|
||||
}
|
||||
|
||||
func ApplyDiceRoll(p *gcsql.Post) (rollSum int, err error) {
|
||||
words := strings.Split(string(p.Message), " ")
|
||||
for w, word := range words {
|
||||
roll := diceRoller.FindStringSubmatch(word)
|
||||
if len(roll) == 0 {
|
||||
continue
|
||||
}
|
||||
numDice := 1
|
||||
if roll[1] != "" {
|
||||
numDice, err = strconv.Atoi(roll[1])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
dieSize, err := strconv.Atoi(roll[2])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if numDice < 1 || dieSize < 1 {
|
||||
return 0, fmt.Errorf("dice roll too small")
|
||||
}
|
||||
for i := 0; i < numDice; i++ {
|
||||
rollSum += rand.Intn(dieSize) + 1
|
||||
switch roll[3] {
|
||||
case "+":
|
||||
mod, err := strconv.Atoi(roll[4])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
rollSum += mod
|
||||
case "-":
|
||||
mod, err := strconv.Atoi(roll[4])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
rollSum -= mod
|
||||
}
|
||||
}
|
||||
words[w] = fmt.Sprintf(`<span class="dice-roll">%dd%d`, numDice, dieSize)
|
||||
if roll[3] != "" {
|
||||
words[w] += roll[3] + roll[4]
|
||||
}
|
||||
words[w] += fmt.Sprintf(" = %d</span>", rollSum)
|
||||
}
|
||||
p.Message = template.HTML(strings.Join(words, " "))
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package posting
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/gochan-org/gochan/pkg/config"
|
||||
"github.com/gochan-org/gochan/pkg/gcsql"
|
||||
"github.com/stretchr/testify/assert"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
@ -34,6 +36,56 @@ end)`
|
|||
luaBBCodeTestExpected = `<span class="lua">Lua test</span>`
|
||||
)
|
||||
|
||||
var (
|
||||
diceTestCases = []diceRollerTestCase{
|
||||
{
|
||||
desc: "[1d6]",
|
||||
post: gcsql.Post{
|
||||
MessageRaw: "before [1d6] after",
|
||||
},
|
||||
matcher: regexp.MustCompile(`before <span class="dice-roll">1d6 = \d</span> after`),
|
||||
expectMin: 1,
|
||||
expectMax: 6,
|
||||
},
|
||||
{
|
||||
desc: "[1d6+1]",
|
||||
post: gcsql.Post{
|
||||
MessageRaw: "before [1d6+1] after",
|
||||
},
|
||||
matcher: regexp.MustCompile(`before <span class="dice-roll">1d6\+1 = \d</span> after`),
|
||||
expectMin: 2,
|
||||
expectMax: 7,
|
||||
},
|
||||
{
|
||||
desc: "[1d6-1]",
|
||||
post: gcsql.Post{
|
||||
MessageRaw: "before [1d6-1] after",
|
||||
},
|
||||
matcher: regexp.MustCompile(`before <span class="dice-roll">1d6-1 = \d</span> after`),
|
||||
expectMin: 0,
|
||||
expectMax: 5,
|
||||
},
|
||||
{
|
||||
desc: "[d8]",
|
||||
post: gcsql.Post{
|
||||
MessageRaw: "[d8]",
|
||||
},
|
||||
matcher: regexp.MustCompile(`<span class="dice-roll">1d8 = \d</span>`),
|
||||
expectMin: 1,
|
||||
expectMax: 8,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type diceRollerTestCase struct {
|
||||
desc string
|
||||
post gcsql.Post
|
||||
expectError bool
|
||||
matcher *regexp.Regexp
|
||||
expectMin int
|
||||
expectMax int
|
||||
}
|
||||
|
||||
func TestBBCode(t *testing.T) {
|
||||
config.SetVersion(versionStr)
|
||||
var testFmtr MessageFormatter
|
||||
|
@ -72,3 +124,35 @@ func TestLuaBBCode(t *testing.T) {
|
|||
assert.Equal(t, "[b]Lua test[/b]", msgfmtr.bbCompiler.Compile("[b]Lua test[/b]"))
|
||||
assert.Error(t, l.DoString(`bbcode.set_tag("lua", 1)`))
|
||||
}
|
||||
|
||||
func diceRollRunner(t *testing.T, tC *diceRollerTestCase) {
|
||||
var err error
|
||||
tC.post.Message, err = FormatMessage(tC.post.MessageRaw, "")
|
||||
assert.NoError(t, err)
|
||||
result, err := ApplyDiceRoll(&tC.post)
|
||||
if tC.expectError {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, 0, result)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Regexp(t, tC.matcher, tC.post.Message)
|
||||
assert.GreaterOrEqual(t, result, tC.expectMin)
|
||||
assert.LessOrEqual(t, result, tC.expectMax)
|
||||
}
|
||||
if t.Failed() {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiceRoll(t *testing.T) {
|
||||
config.SetVersion(versionStr)
|
||||
msgfmtr.Init()
|
||||
for _, tC := range diceTestCases {
|
||||
t.Run(tC.desc, func(t *testing.T) {
|
||||
for i := 0; i < 100; i++ {
|
||||
// Run the test case multiple times to account for randomness
|
||||
diceRollRunner(t, &tC)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ func luaTableToHTMLTag(l *lua.LState, table *lua.LTable) (*bbcode.HTMLTag, error
|
|||
switch attrsLV.Type() {
|
||||
case lua.LTTable:
|
||||
attrsLT := attrsLV.(*lua.LTable)
|
||||
fmt.Println("attrs size:", attrsLT.Len())
|
||||
attrsLT.ForEach(func(key, val lua.LValue) {
|
||||
if tag.Attrs == nil {
|
||||
tag.Attrs = make(map[string]string)
|
||||
|
|
|
@ -226,6 +226,10 @@ func doFormatting(post *gcsql.Post, board *gcsql.Board, request *http.Request, e
|
|||
errEv.Err(err).Caller().Msg("Unable to format message")
|
||||
return errors.New("unable to format message")
|
||||
}
|
||||
if _, err = ApplyDiceRoll(post); err != nil {
|
||||
errEv.Err(err).Caller().Msg("Error applying dice roll")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue