0
0
mirror of https://github.com/rls-moe/nyx synced 2025-08-19 06:18:38 +00:00

Added Moderation Tools, Captcha & Trollthrottle

This commit is contained in:
Tim Schuster
2017-03-13 16:04:00 +01:00
parent 69b0d20825
commit 4177901714
33 changed files with 9696 additions and 261 deletions

View File

@@ -43,7 +43,7 @@ func serveBoard(w http.ResponseWriter, r *http.Request) {
errw.ErrorWriter(err, w, r)
return
}
err = boardTmpl.Execute(dat, ctx)
err = tmpls.ExecuteTemplate(dat, "board/board", ctx)
if err != nil {
errw.ErrorWriter(err, w, r)
return

View File

@@ -2,6 +2,7 @@ package board
import (
"bytes"
"errors"
"github.com/GeertJohan/go.rice"
"github.com/pressly/chi"
"github.com/tidwall/buntdb"
@@ -24,26 +25,48 @@ var riceConf = rice.Config{
var box = riceConf.MustFindBox("http/board/res/")
var (
dirTmpl = template.New("board/dir")
boardTmpl = template.New("board/board")
threadTmpl = template.New("board/thread")
tmpls = template.New("base")
//dirTmpl = template.New("board/dir")
//boardTmpl = template.New("board/board")
//threadTmpl = template.New("board/thread")
hdlFMap = template.FuncMap{
"renderText": resources.OperateReplyText,
"dict": func(values ...interface{}) (map[string]interface{}, error) {
if len(values)%2 != 0 {
return nil, errors.New("invalid dict call")
}
dict := make(map[string]interface{}, len(values)/2)
for i := 0; i < len(values); i += 2 {
key, ok := values[i].(string)
if !ok {
return nil, errors.New("dict keys must be strings")
}
dict[key] = values[i+1]
}
return dict, nil
},
"rateSpam": resources.SpamScore,
"makeCaptcha": resources.MakeCaptcha,
}
)
func init() {
var err error
dirTmpl, err = dirTmpl.Parse(box.MustString("dir.html"))
tmpls = tmpls.Funcs(hdlFMap)
tmpls, err = tmpls.New("thread/postlists").Parse(box.MustString("thread.tmpl.html"))
if err != nil {
panic(err)
}
boardTmpl, err = boardTmpl.Funcs(hdlFMap).Parse(box.MustString("board.html"))
_, err = tmpls.New("board/dir").Parse(box.MustString("dir.html"))
if err != nil {
panic(err)
}
threadTmpl, err = threadTmpl.Funcs(hdlFMap).Parse(box.MustString("thread.html"))
_, err = tmpls.New("board/board").Parse(box.MustString("board.html"))
if err != nil {
panic(err)
}
_, err = tmpls.New("board/thread").Parse(box.MustString("thread.html"))
if err != nil {
panic(err)
}
@@ -57,6 +80,9 @@ func Router(r chi.Router) {
r.Get("/:board/:thread/thread.html", serveThread)
r.Get("/:board/:thread/:post/post.html", servePost)
r.Post("/:board/:thread/reply.sh", handleNewReply)
r.Handle("/captcha/:captchaId.png", resources.ServeCaptcha)
r.Handle("/captcha/:captchaId.wav", resources.ServeCaptcha)
r.Handle("/captcha/download/:captchaId.wav", resources.ServeCaptcha)
}
func servePost(w http.ResponseWriter, r *http.Request) {
@@ -79,7 +105,7 @@ func serveDir(w http.ResponseWriter, r *http.Request) {
errw.ErrorWriter(err, w, r)
return
}
err = dirTmpl.Execute(dat, ctx)
err = tmpls.ExecuteTemplate(dat, "board/dir", ctx)
if err != nil {
errw.ErrorWriter(err, w, r)
return

View File

@@ -18,6 +18,14 @@ func handleNewReply(w http.ResponseWriter, r *http.Request) {
return
}
if !resources.VerifyCaptcha(r) {
http.Redirect(w, r,
fmt.Sprintf("/%s/%s/thread.html?err=wrong_captcha",
chi.URLParam(r, "board"), chi.URLParam(r, "thread")),
http.StatusSeeOther)
return
}
var reply = &resources.Reply{}
reply.Board = chi.URLParam(r, "board")
@@ -26,9 +34,9 @@ func handleNewReply(w http.ResponseWriter, r *http.Request) {
errw.ErrorWriter(err, w, r)
return
}
reply.Thread = int64(tid)
reply.Thread = tid
reply.Text = r.FormValue("text")
if len(reply.Text) > 1000 {
if len(reply.Text) > 10000 {
errw.ErrorWriter(errw.MakeErrorWithTitle("I'm sorry but I can't do that", "These are too many characters"), w, r)
return
}
@@ -36,6 +44,15 @@ func handleNewReply(w http.ResponseWriter, r *http.Request) {
errw.ErrorWriter(errw.MakeErrorWithTitle("I'm sorry but I can't do that", "These are not enough characters"), w, r)
return
}
if score, err := resources.SpamScore(reply.Text); err != nil || !resources.CaptchaPass(score) {
http.Redirect(w, r,
fmt.Sprintf("/%s/%s/thread.html?err=trollthrottle",
chi.URLParam(r, "board"), chi.URLParam(r, "thread")),
http.StatusSeeOther)
return
}
reply.Metadata = map[string]string{}
if r.FormValue("tripcode") != "" {
reply.Metadata["trip"] = resources.CalcTripCode(r.FormValue("tripcode"))

View File

@@ -17,13 +17,21 @@ func handleNewThread(w http.ResponseWriter, r *http.Request) {
return
}
if !resources.VerifyCaptcha(r) {
http.Redirect(w, r,
fmt.Sprintf("/%s/board.html?err=wrong_captcha",
chi.URLParam(r, "board")),
http.StatusSeeOther)
return
}
var thread = &resources.Thread{}
var mainReply = &resources.Reply{}
mainReply.Board = chi.URLParam(r, "board")
thread.Board = chi.URLParam(r, "board")
mainReply.Text = r.FormValue("text")
if len(mainReply.Text) > 1000 {
if len(mainReply.Text) > 10000 {
errw.ErrorWriter(errw.MakeErrorWithTitle("I'm sorry but I can't do that", "These are too many characters"), w, r)
return
}
@@ -31,6 +39,15 @@ func handleNewThread(w http.ResponseWriter, r *http.Request) {
errw.ErrorWriter(errw.MakeErrorWithTitle("I'm sorry but I can't do that", "These are not enough characters"), w, r)
return
}
if score, err := resources.SpamScore(mainReply.Text); err != nil || !resources.CaptchaPass(score) {
http.Redirect(w, r,
fmt.Sprintf("/%s/board.html?err=trollthrottle",
chi.URLParam(r, "board")),
http.StatusSeeOther)
return
}
mainReply.Metadata = map[string]string{}
if r.FormValue("tripcode") != "" {
mainReply.Metadata["trip"] = resources.CalcTripCode(r.FormValue("tripcode"))

View File

@@ -13,106 +13,22 @@
<div class="site description"><h2>{{.Board.LongName}}</h2></div>
</div>
{{ $boardlink := .Board.ShortName }}
<div class="postarea">
<form id="postform" action="/{{$boardlink}}/new_thread.sh" method="POST">
<table>
<tbody>
<tr>
<td class="postblock">
TripCode
</td>
<td>
<input type="text" name="tripcode" size=48 placeholder="Anonymous"/>
<input
type="hidden"
name="csrf_token"
value="{{ .CSRFToken }}" />
</td>
</tr>
<tr>
<td class="postblock">
Comment
</td>
<td>
<textarea
name="text"
placeholder="your comment"
rows="4"
cols="48"
minlength="10"
required
></textarea>
</td>
</tr>
<tr>
<td class="postblock">
Image File
</td>
<td>
<input type="file" name="image" />
</td>
</tr>
{{ if ne .Config.Captcha.Mode "disabled" }}
<tr>
<td class="postblock">
Captcha
</td>
<td>
<input type="text" name="captcha" size=48 />
<input type="hidden"
value="{{.CaptchaToken}}"/>
<img alt=""
src="{{.CaptchaImage}}" />
</td>
</tr>
{{ end }}
<tr>
<td class="postblock">
</td>
<td>
<input type="submit" value="Post" />
</td>
</tr>
</tbody>
</table>
</form>
</div>
{{ if .Session }}
{{ if eq (.Session.CAttr "mode") "admin" }}
Logged in as Admin
{{ end }}
{{ if eq (.Session.CAttr "mode") "mod" }}
Logged in as Mod for {{ .Session.CAttr "board" }}
{{ end }}
{{ end }}
<hr />
{{ template "thread/post" . }}
<div class="postlists">
{{ $board := .Board }}
{{ $csrf := .CSRFToken }}
{{ $session := .Session }}
{{range .Threads}}
{{ $threadrid := .GetReply.ID }}
<label><span class="postertrip">
{{ if .GetReply.Metadata.trip }}
{{.GetReply.Metadata.trip}}
{{ else }}
Anonymous
{{ end }}
</span></label>
<span class="reflink"><a href="/{{$boardlink}}/{{.ID}}/thread.html">No.{{.ID}}</a></span>
<blockquote><blockquote>
{{ renderText .GetReply.Text}}
</blockquote></blockquote>
{{range .GetReplies}}
{{ if ne .ID $threadrid }}
<table><tbody><tr><td class="doubledash">&gt;&gt;</td>
<td class="reply" id="reply{{.ID}}">
<label><span class="postertrip">
{{ if .Metadata.trip }}
{{.Metadata.trip}}
{{ else }}
Anonymous
{{ end }}
</span></label>
<span class="reflink"><a href="/{{$boardlink}}/{{.Thread}}/thread.html">No.{{.ID}}</a></span>
<blockquote><blockquote>
{{ renderText .Text}}
</blockquote></blockquote>
</td>
</tr></tbody></table>
{{end}}
{{end}}
<br clear="left" /><hr />
{{ template "thread/postlists" dict "Thread" . "Board" $board "CSRFToken" $csrf "Session" $session }}
{{end}}
</div>
</body>

View File

@@ -11,119 +11,11 @@
<div class="banner logo">
<div class="site title"><h1><span class="reflink"><a href="/{{.Board.ShortName}}/board.html">/{{.Board.ShortName}}/</a></span></h1></div>
<div class="site description"><h2>{{.Board.LongName}}</h2></div>
<div class="site thread"><h3>{{.Thread.ID}}</h3></div>
</div>
{{ $boardlink := .Board.ShortName }}
<hr />
<div class="postarea">
<form id="postform" action="/{{.Board.ShortName}}/{{.Thread.ID}}/reply.sh" method="POST">
<table>
<tbody>
<tr>
<td class="postblock">
TripCode
</td>
<td>
<input type="text" name="tripcode" size=48 placeholder="Anonymous"/>
<input
type="hidden"
name="csrf_token"
value="{{ .CSRFToken }}" />
</td>
</tr>
<tr>
<td class="postblock">
Comment
</td>
<td>
<textarea
name="text"
placeholder="your comment"
rows="4"
cols="48"
minlength="10"
required
></textarea>
</td>
</tr>
<tr>
<td class="postblock">
Image File
</td>
<td>
<input type="file" name="image" />
</td>
</tr>
{{ if ne .Config.Captcha.Mode "disabled" }}
<tr>
<td class="postblock">
Captcha
</td>
<td>
<input type="text" name="captcha" size=48 />
<input type="hidden"
value="{{.CaptchaToken}}"/>
<img alt=""
src="{{.CaptchaImage}}" />
</td>
</tr>
{{ end }}
<tr>
<td class="postblock">
</td>
<td>
<input type="submit" value="Post" />
</td>
</tr>
{{ if .Board.Metadata.rules }}
<tr>
<td class="postblock">
Rules
</td>
<td>
{{ .Board.Metadata.rules }}
</td>
</tr>
{{ end }}
</tbody>
</table>
</form>
</div>
<div class="postlists">
{{with .Thread }}
{{ $threadrid := .GetReply.ID }}
<label><span class="postertrip">
{{ if .GetReply.Metadata.trip }}
{{.GetReply.Metadata.trip}}
{{ else }}
Anonymous
{{ end }}
</span></label>
<span class="reflink"><a href="/{{$boardlink}}/{{.ID}}/thread.html">No.{{.ID}}</a></span>
<blockquote><blockquote>
{{ renderText .GetReply.Text}}
</blockquote></blockquote>
{{range .GetReplies}}
{{ if ne .ID $threadrid }}
<table><tbody><tr><td class="doubledash">&gt;&gt;</td>
<td class="reply" id="reply{{.ID}}">
<label><span class="postertrip">
{{ if .Metadata.trip }}
{{.Metadata.trip}}
{{ else }}
Anonymous
{{ end }}
</span></label>
<span class="reflink"><a href="/{{$boardlink}}/{{.Thread}}/thread.html">No.{{.ID}}</a></span>
<blockquote><blockquote>
{{ renderText .Text}}
</blockquote></blockquote>
</td>
</tr></tbody></table>
{{end}}
{{end}}
<br clear="left" /><hr />
{{end}}
</div>
{{ template "thread/post" . }}
{{ template "thread/postlists" . }}
</body>
</html>

View File

@@ -0,0 +1,169 @@
{{ define "thread/post" }}
<div class="postarea">
{{ if .Thread }}
<form id="postform"
action="/{{.Board.ShortName}}/{{.Thread.ID}}/reply.sh" method="POST">
{{ else }}
<form id="postform"
action="/{{.Board.ShortName}}/new_thread.sh" method="POST">
{{ end }}
<table>
<tbody>
{{ if .PreviousError }}
<tr>
<td class="postblock">
Error
</td>
<td>
{{.PreviousError}}
</td>
</tr>
{{ end }}
<tr>
<td class="postblock">
TripCode
</td>
<td>
<input type="text" name="tripcode" size=48 placeholder="Anonymous"/>
<input
type="hidden"
name="csrf_token"
value="{{ .CSRFToken }}" />
</td>
</tr>
<tr>
<td class="postblock">
Comment
</td>
<td>
<textarea
name="text"
placeholder="your comment"
rows="4"
cols="48"
minlength="10"
required
></textarea>
</td>
</tr>
<tr>
<td class="postblock">
Image File
</td>
<td>
<input type="file" name="image" />
</td>
</tr>
{{ if ne .Config.Captcha.Mode "disabled" }}
<tr>
<td class="postblock">
Captcha
</td>
<td>
{{ $captchaId := makeCaptcha }}
<img id="image" src="/captcha/{{$captchaId}}.png" alt="Captcha Image"/>
<audio id=audio controls style="display:none" src="/captcha/{{$captchaId}}.wav" preload=none>
You browser doesn't support audio.
<a href="/captcha/download/{{$captchaId}}.wav">Download file</a> to play it in the external player.
</audio>
<br>
<input type="text" name="captchaSolution" size=48 />
<input type="hidden"
name="captchaId"
value="{{$captchaId}}"/>
</td>
</tr>
{{ end }}
<tr>
<td class="postblock">
</td>
<td>
<input type="submit" value="Post" />
</td>
</tr>
</tbody>
</table>
</form>
</div>
<hr />
{{ end }}
{{ define "thread/reply" }}
<label><span class="postertrip">
{{ if .Reply.Metadata.trip }}
{{ .Reply.Metadata.trip}}
{{ else }}
Anonymous
{{ end }}
</span></label>
<span class="reflink">
<a href="/{{.Boardlink}}/{{.ThreadID}}/thread.html">No.{{.Reply.ID}}</a>
</span>
{{ if .Session }}
{{ if eq (.Session.CAttr "mode") "admin" }}
<form class="delform" action="/mod/del_reply.sh" method="POST">
<input
type="hidden"
name="csrf_token"
value="{{ .CSRF }}" />
<input
type="hidden"
name="reply_id"
value="{{ .Reply.ID }}" />
<input
type="hidden"
name="thread_id"
value="{{ .ThreadID }}" />
<input
type="hidden"
name="board"
value="{{ .Boardlink }}" />
<input type="submit" value="delete" />
</form>
{{ end }}
{{ end }}
<span>
{{printf "[SpamScore: %f]" (rateSpam .Reply.Text) }}
</span>
{{ if .Reply.Metadata.deleted }}
<blockquote><blockquote class="deleted">
{{ renderText .Reply.Text }}
</blockquote></blockquote>
{{ else }}
<blockquote><blockquote>
{{ renderText .Reply.Text}}
</blockquote></blockquote>
{{ end }}
{{ end }}
{{ define "thread/main" }}
<div class="postlists">
{{ $boardlink := .Board.ShortName }}
{{ $threadrid := .Thread.GetReply.ID }}
{{ $threadid := .Thread.ID }}
{{ $csrf := .CSRFToken }}
{{ $session := .Session }}
{{ with .Thread }}
{{ with .GetReply }}
{{ with dict "Reply" . "Boardlink" $boardlink "CSRF" $csrf "ThreadID" $threadid "Session" $session }}
{{ template "thread/reply" . }}
{{ end }}
{{ end }}
{{range .GetReplies}}
{{ if ne .ID $threadrid }}
<table><tbody><tr><td class="doubledash">&gt;&gt;</td>
<td class="reply" id="reply{{.ID}}">
{{ with dict "Reply" . "Boardlink" $boardlink "CSRF" $csrf "ThreadID" $threadid "Session" $session }}
{{ template "thread/reply" . }}
{{ end }}
</td>
</tr></tbody></table>
{{end}}
{{end}}
{{end}}
<br clear="left" /><hr />
</div>
{{ end }}
{{ template "thread/main" . }}

View File

@@ -28,7 +28,7 @@ func serveThread(w http.ResponseWriter, r *http.Request) {
if err != nil {
return err
}
thread, err := resources.GetThread(tx, r.Host, bName, int64(id))
thread, err := resources.GetThread(tx, r.Host, bName, id)
if err != nil {
return err
}
@@ -48,7 +48,7 @@ func serveThread(w http.ResponseWriter, r *http.Request) {
errw.ErrorWriter(err, w, r)
return
}
err = threadTmpl.Execute(dat, ctx)
err = tmpls.ExecuteTemplate(dat, "board/thread", ctx)
if err != nil {
errw.ErrorWriter(err, w, r)
return