1
0
Fork 0
mirror of https://github.com/Eggbertx/gochan.git synced 2025-09-05 11:06:23 -07:00
gochan/pkg/manage/actionsModPerm.go

1039 lines
31 KiB
Go

package manage
import (
"bytes"
"database/sql"
"errors"
"fmt"
"net"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/gochan-org/gochan/pkg/building"
"github.com/gochan-org/gochan/pkg/config"
"github.com/gochan-org/gochan/pkg/gcsql"
"github.com/gochan-org/gochan/pkg/gctemplates"
"github.com/gochan-org/gochan/pkg/gcutil"
"github.com/gochan-org/gochan/pkg/posting/uploads"
"github.com/gochan-org/gochan/pkg/server/serverutil"
"github.com/rs/zerolog"
)
// manage actions that require moderator-level permission go here
var (
filterFields = []filterField{
{Value: "name", Text: "Name"},
{Value: "trip", Text: `Tripcode`},
{Value: "email", Text: "Email"},
{Value: "subject", Text: "Subject"},
{Value: "body", Text: "Message body"},
{Value: "firsttimeboard", Text: "First time poster (board)"},
{Value: "notfirsttimeboard", Text: "Not a first time poster (board)"},
{Value: "firsttimesite", Text: "First time poster (site-wide)"},
{Value: "notfirsttimesite", Text: "Not a first time poster (site-wide)"},
{Value: "isop", Text: "Is OP"},
{Value: "notop", Text: "Is reply"},
{Value: "hasfile", Text: "Has file"},
{Value: "nofile", Text: "No file"},
{Value: "filename", Text: "Filename"},
{Value: "checksum", Text: "File checksum"},
{Value: "ahash", Text: "Image fingerprint"},
{Value: "useragent", Text: "User agent"},
}
filterActionsMap = map[string]string{
"reject": "Reject post",
"ban": "Ban IP",
"log": "Log match",
}
)
func bansCallback(_ http.ResponseWriter, request *http.Request, staff *gcsql.Staff, _ bool, infoEv *zerolog.Event, errEv *zerolog.Event) (output interface{}, err error) {
var outputStr string
var ban gcsql.IPBan
ban.StaffID = staff.ID
deleteIDStr := request.FormValue("delete")
postIDstr := request.FormValue("postid")
if deleteIDStr != "" {
// deleting a ban
ban.ID, err = strconv.Atoi(deleteIDStr)
if err != nil {
errEv.Err(err).Caller().
Str("deleteBan", deleteIDStr).
Send()
return "", err
}
if err = ban.Deactivate(staff.ID); err != nil {
errEv.Err(err).Caller().
Int("deleteBan", ban.ID).
Send()
return "", err
}
} else if request.FormValue("do") == "add" {
ip := request.PostFormValue("ip")
ban.RangeStart, ban.RangeEnd, err = gcutil.ParseIPRange(ip)
if err != nil {
errEv.Err(err).Caller().
Str("ip", ip)
return "", err
}
gcutil.LogStr("rangeStart", ban.RangeStart, infoEv, errEv)
gcutil.LogStr("rangeEnd", ban.RangeEnd, infoEv, errEv)
gcutil.LogStr("reason", ban.Message, infoEv, errEv)
gcutil.LogBool("appealable", ban.CanAppeal, infoEv, errEv)
err := ipBanFromRequest(&ban, request, infoEv, errEv)
if err != nil {
errEv.Err(err).Caller().
Msg("unable to submit ban")
return "", err
}
infoEv.Msg("Added IP ban")
} else if postIDstr != "" {
postID, err := strconv.Atoi(postIDstr)
if err != nil {
errEv.Err(err).Caller().Send()
return "", err
}
if ban.RangeStart, err = gcsql.GetPostIP(postID); err != nil {
errEv.Err(err).Caller().
Int("postID", postID).Send()
return "", err
}
ban.RangeEnd = ban.RangeStart
}
filterBoardIDstr := request.FormValue("filterboardid")
var filterBoardID int
if filterBoardIDstr != "" {
if filterBoardID, err = strconv.Atoi(filterBoardIDstr); err != nil {
errEv.Err(err).Caller().
Str("filterboardid", filterBoardIDstr).Send()
return "", err
}
}
limitStr := request.FormValue("limit")
limit := 200
if limitStr != "" {
if limit, err = strconv.Atoi(limitStr); err != nil {
errEv.Err(err).Caller().
Str("limit", limitStr).Send()
return "", err
}
}
banlist, err := gcsql.GetIPBans(filterBoardID, limit, true)
if err != nil {
errEv.Err(err).Caller().Msg("Error getting ban list")
err = errors.New("Error getting ban list: " + err.Error())
return "", err
}
manageBansBuffer := bytes.NewBufferString("")
if err = serverutil.MinifyTemplate(gctemplates.ManageBans, map[string]interface{}{
"banlist": banlist,
"allBoards": gcsql.AllBoards,
"ban": ban,
"filterboardid": filterBoardID,
}, manageBansBuffer, "text/html"); err != nil {
errEv.Err(err).Str("template", "manage_bans.html").Caller().Send()
return "", errors.New("Error executing ban management page template: " + err.Error())
}
outputStr += manageBansBuffer.String()
return outputStr, nil
}
func appealsCallback(_ http.ResponseWriter, request *http.Request, staff *gcsql.Staff, wantsJSON bool, infoEv, errEv *zerolog.Event) (output interface{}, err error) {
banIDstr := request.FormValue("banid")
var banID int
if banIDstr != "" {
if banID, err = strconv.Atoi(banIDstr); err != nil {
errEv.Err(err).Caller().Send()
return "", err
}
}
infoEv.Int("banID", banID)
limitStr := request.FormValue("limit")
limit := 20
if limitStr != "" {
if limit, err = strconv.Atoi(limitStr); err != nil {
errEv.Err(err).Caller().Send()
return "", err
}
}
approveStr := request.FormValue("approve")
if approveStr != "" {
// approving an appeal
approveID, err := strconv.Atoi(approveStr)
if err != nil {
errEv.Err(err).Caller().
Str("approveStr", approveStr).Send()
}
if err = gcsql.ApproveAppeal(approveID, staff.ID); err != nil {
errEv.Err(err).Caller().
Int("approveAppeal", approveID).Send()
return "", err
}
}
appeals, err := gcsql.GetAppeals(banID, limit)
if err != nil {
errEv.Err(err).Caller().Send()
return "", errors.New("Unable to get appeals: " + err.Error())
}
if wantsJSON {
return appeals, nil
}
manageAppealsBuffer := bytes.NewBufferString("")
pageData := map[string]interface{}{}
if len(appeals) > 0 {
pageData["appeals"] = appeals
}
if err = serverutil.MinifyTemplate(gctemplates.ManageAppeals, pageData, manageAppealsBuffer, "text/html"); err != nil {
errEv.Err(err).Str("template", "manage_appeals.html").Caller().Send()
return "", errors.New("Error executing appeal management page template: " + err.Error())
}
return manageAppealsBuffer.String(), err
}
/* func fileBansCallback(_ http.ResponseWriter, request *http.Request, staff *gcsql.Staff, wantsJSON bool, infoEv, errEv *zerolog.Event) (output interface{}, err error) {
delFilenameBanIDStr := request.FormValue("delfnb") // filename ban deletion
delChecksumBanIDStr := request.FormValue("delcsb") // checksum ban deletion
boardidStr := request.FormValue("boardid")
boardid := 0
if boardidStr != "" {
boardid, err = strconv.Atoi(boardidStr)
if err != nil {
errEv.Err(err).Caller().
Str("boardid", boardidStr).Send()
return "", err
}
}
gcutil.LogInt("boardid", boardid, infoEv, errEv)
staffnote := request.FormValue("staffnote")
if request.FormValue("dofilenameban") != "" {
// creating a new filename ban
filename := request.FormValue("filename")
isRegex := request.FormValue("isregex") == "on"
if isRegex {
_, err = regexp.Compile(filename)
if err != nil {
// invalid regular expression
errEv.Err(err).Caller().
Str("regex", filename).Send()
return "", err
}
}
if _, err = gcsql.NewFilenameBan(filename, isRegex, boardid, staff.ID, staffnote); err != nil {
errEv.Err(err).Caller().
Str("filename", filename).
Bool("isregex", isRegex).Send()
return "", err
}
infoEv.
Str("filename", filename).
Bool("isregex", isRegex).
Msg("Created new filename ban")
if wantsJSON {
return "success", nil
}
} else if delFilenameBanIDStr != "" {
delFilenameBanID, err := strconv.Atoi(delFilenameBanIDStr)
if err != nil {
errEv.Err(err).Caller().
Str("delfnb", delFilenameBanIDStr).
Send()
return "", err
}
var fnb gcsql.FilenameBan
fnb.ID = delFilenameBanID
if err = fnb.Deactivate(staff.ID); err != nil {
errEv.Err(err).Caller().
Int("deleteFilenameBanID", delFilenameBanID).Send()
return "", err
}
infoEv.
Int("deleteFilenameBanID", delFilenameBanID).
Msg("Filename ban deleted")
if wantsJSON {
return "success", nil
}
} else if request.PostFormValue("dochecksumban") != "" {
// creating a new file checksum ban
checksum := request.PostFormValue("checksum")
ipBan := request.PostFormValue("ban") == "on"
var reason string
if ipBan {
reason = request.PostFormValue("banmsg")
if reason == "" {
return "", errors.New("ban reason required if IP ban is set")
}
}
gcutil.LogBool("ipBan", ipBan, infoEv, errEv)
fingerprinter := request.PostFormValue("fingerprinter")
if fingerprinter == "checksum" {
fingerprinter = ""
}
gcutil.LogStr("fingerprinter", fingerprinter, infoEv, errEv)
if _, err = gcsql.NewFileChecksumBan(
checksum, fingerprinter, boardid, staff.ID, staffnote, ipBan, reason,
); err != nil {
errEv.Err(err).Caller().
Str("checksum", checksum).Send()
return "", err
}
infoEv.
Str("checksum", checksum).
Msg("Created new file checksum ban")
if wantsJSON {
return "success", nil
}
} else if delChecksumBanIDStr != "" {
// user requested a checksum ban ID to delete
delChecksumBanID, err := strconv.Atoi(delChecksumBanIDStr)
if err != nil {
errEv.Err(err).Caller().
Str("deleteChecksumBanIDStr", delChecksumBanIDStr).Send()
return "", err
}
if err = (gcsql.FileBan{ID: delChecksumBanID}).Deactivate(staff.ID); err != nil {
errEv.Err(err).Caller().
Int("deleteChecksumBanID", delChecksumBanID).Send()
return "", err
}
infoEv.Int("deleteChecksumBanID", delChecksumBanID).Msg("File checksum ban deleted")
if wantsJSON {
return "success", nil
}
}
filterBoardIDstr := request.FormValue("filterboardid")
var filterBoardID int
if filterBoardIDstr != "" {
if filterBoardID, err = strconv.Atoi(filterBoardIDstr); err != nil {
errEv.Err(err).Caller().
Str("filterboardid", filterBoardIDstr).Send()
return "", err
}
}
limitStr := request.FormValue("limit")
limit := 200
if limitStr != "" {
if limit, err = strconv.Atoi(limitStr); err != nil {
errEv.Err(err).Caller().
Str("limit", limitStr).Send()
return "", err
}
}
checksumBans, err := gcsql.GetFileBans(filterBoardID, limit)
if err != nil {
return "", err
}
filenameBans, err := gcsql.GetFilenameBans(filterBoardID, limit)
if err != nil {
return "", err
}
manageBansBuffer := bytes.NewBufferString("")
if err = serverutil.MinifyTemplate(gctemplates.ManageFileBans, map[string]interface{}{
"allBoards": gcsql.AllBoards,
"checksumBans": checksumBans,
"filenameBans": filenameBans,
"filterboardid": filterBoardID,
"currentStaff": staff.Username,
}, manageBansBuffer, "text/html"); err != nil {
errEv.Err(err).Str("template", "manage_filebans.html").Caller().Send()
return "", errors.New("Error executing ban management page template: " + err.Error())
}
outputStr := manageBansBuffer.String()
return outputStr, nil
} */
type filterField struct {
Value string
Text string
}
func filtersCallback(_ http.ResponseWriter, request *http.Request, staff *gcsql.Staff, wantsJSON bool, infoEv, errEv *zerolog.Event) (output any, err error) {
// doFilterAdd = request.PostFormValue("dofilteradd")
// filterID := request.FormValue("filterid")
// editFilter := request.FormValue("editfilter")
disableFilterIDStr := request.FormValue("disable")
enableFilterIDStr := request.FormValue("enable")
if disableFilterIDStr != "" {
disableFilterID, err := strconv.Atoi(disableFilterIDStr)
if err != nil {
return nil, err
}
gcsql.SetFilterActive(disableFilterID, false)
} else if enableFilterIDStr != "" {
enableFilterID, err := strconv.Atoi(enableFilterIDStr)
if err != nil {
return nil, err
}
gcsql.SetFilterActive(enableFilterID, true)
}
showStr := request.FormValue("show")
var show gcsql.ActiveFilter
switch showStr {
case "active":
show = gcsql.OnlyActiveFilters
case "inactive":
show = gcsql.OnlyInactiveFilters
default:
show = gcsql.AllFilters
}
var filters []gcsql.Filter
boardSearch := request.FormValue("boardsearch")
if boardSearch == "" {
filters, err = gcsql.GetAllFilters(show)
} else {
filters, err = gcsql.GetFiltersByBoardDir(boardSearch, false, show)
}
if err != nil {
errEv.Err(err).Caller().Msg("Unable to get filter list")
return nil, err
}
fieldsMap := make(map[string]string)
for _, ff := range filterFields {
fieldsMap[ff.Value] = ff.Text
}
var staffUsernames []string
var conditionsText []string
for _, filter := range filters {
if _, ok := filterActionsMap[filter.MatchAction]; !ok {
return nil, gcsql.ErrInvalidMatchAction
}
conditions, err := filter.Conditions()
if err != nil {
errEv.Err(err).Caller().Int("filterID", filter.ID).Msg("Unable to get filter conditions")
return nil, err
}
var filterConditionsText string
for _, condition := range conditions {
text, ok := fieldsMap[condition.Field]
if !ok {
return nil, gcsql.ErrInvalidConditionField
}
filterConditionsText += text + ","
}
filterConditionsText = strings.TrimRight(filterConditionsText, ",")
conditionsText = append(conditionsText, filterConditionsText)
username, err := gcsql.GetStaffUsernameFromID(*filter.StaffID)
if err != nil {
return nil, err
}
staffUsernames = append(staffUsernames, username)
}
var buf bytes.Buffer
if err = serverutil.MinifyTemplate(gctemplates.ManageFilters, map[string]any{
"allBoards": gcsql.AllBoards,
"fields": filterFields,
"filters": filters,
"conditions": conditionsText,
"actions": filterActionsMap,
"staff": staffUsernames,
"show": showStr,
"boardSearch": boardSearch,
}, &buf, "text/html"); err != nil {
errEv.Err(err).Caller().Str("template", gctemplates.ManageFilters).Send()
return "", errors.New("Unable to execute filter management template: " + err.Error())
}
return buf.String(), nil
}
/* func nameBansCallback(_ http.ResponseWriter, request *http.Request, staff *gcsql.Staff, _ bool, _, errEv *zerolog.Event) (output interface{}, err error) {
doNameBan := request.FormValue("donameban")
deleteIDstr := request.FormValue("del")
if deleteIDstr != "" {
deleteID, err := strconv.Atoi(deleteIDstr)
if err != nil {
errEv.Err(err).Caller().
Str("delStr", deleteIDstr).Send()
return "", err
}
if err = gcsql.DeleteNameBan(deleteID); err != nil {
errEv.Err(err).Caller().
Int("deleteID", deleteID).
Msg("Unable to delete name ban")
return "", errors.New("Unable to delete name ban: " + err.Error())
}
}
data := map[string]interface{}{
"currentStaff": staff.Username,
"allBoards": gcsql.AllBoards,
}
if doNameBan == "Create" {
var name string
if name, err = getStringField("name", staff.Username, request); err != nil {
return "", err
}
if name == "" {
return "", errors.New("name field must not be empty in name ban submission")
}
var boardID int
if boardID, err = getIntField("boardid", staff.Username, request); err != nil {
return "", err
}
isRegex := request.FormValue("isregex") == "on"
if _, err = gcsql.NewNameBan(name, isRegex, boardID, staff.ID, request.FormValue("staffnote")); err != nil {
errEv.Err(err).Caller().
Str("name", name).
Int("boardID", boardID).Send()
return "", err
}
}
if data["nameBans"], err = gcsql.GetNameBans(0, 0); err != nil {
return "", err
}
buf := bytes.NewBufferString("")
if err = serverutil.MinifyTemplate(gctemplates.ManageNameBans, data, buf, "text/html"); err != nil {
errEv.Err(err).Str("template", "manage_namebans.html").Caller().Send()
return "", errors.New("Error executing name ban management page template: " + err.Error())
}
return buf.String(), nil
} */
func ipSearchCallback(_ http.ResponseWriter, request *http.Request, staff *gcsql.Staff, _ bool, _ *zerolog.Event, errEv *zerolog.Event) (output interface{}, err error) {
ipQuery := request.FormValue("ip")
limitStr := request.FormValue("limit")
data := map[string]interface{}{
"ipQuery": ipQuery,
"limit": 20,
}
if ipQuery != "" && limitStr != "" {
var limit int
if limit, err = strconv.Atoi(limitStr); err == nil && limit > 0 {
data["limit"] = limit
}
var names []string
if names, err = net.LookupAddr(ipQuery); err == nil {
data["reverseAddrs"] = names
} else {
data["reverseAddrs"] = []string{err.Error()}
}
data["posts"], err = building.GetBuildablePostsByIP(ipQuery, limit)
if err != nil {
errEv.Err(err).Caller().
Str("ipQuery", ipQuery).
Int("limit", limit).
Bool("onlyNotDeleted", true).
Send()
return "", fmt.Errorf("Error getting list of posts from %q by staff %s: %s", ipQuery, staff.Username, err.Error())
}
}
manageIpBuffer := bytes.NewBufferString("")
if err = serverutil.MinifyTemplate(gctemplates.ManageIPSearch, data, manageIpBuffer, "text/html"); err != nil {
errEv.Err(err).Caller().
Str("template", "manage_ipsearch.html").Send()
return "", errors.New("Error executing IP search page template:" + err.Error())
}
return manageIpBuffer.String(), nil
}
func reportsCallback(_ http.ResponseWriter, request *http.Request, staff *gcsql.Staff, wantsJSON bool, infoEv *zerolog.Event, errEv *zerolog.Event) (output interface{}, err error) {
dismissIDstr := request.FormValue("dismiss")
if dismissIDstr != "" {
// staff is dismissing a report
dismissID := gcutil.HackyStringToInt(dismissIDstr)
block := request.FormValue("block")
if block != "" && staff.Rank != 3 {
errEv.Caller().
Int("postID", dismissID).
Str("rejected", "not an admin").Send()
return "", errors.New("only the administrator can block reports")
}
found, err := gcsql.ClearReport(dismissID, staff.ID, block != "" && staff.Rank == 3)
if err != nil {
errEv.Err(err).Caller().
Int("postID", dismissID).Send()
return nil, err
}
if !found {
return nil, errors.New("no matching reports")
}
infoEv.
Int("reportID", dismissID).
Bool("blocked", block != "").
Msg("Report cleared")
}
rows, err := gcsql.QuerySQL(`SELECT id,
handled_by_staff_id as staff_id,
(SELECT username FROM DBPREFIXstaff WHERE id = DBPREFIXreports.handled_by_staff_id) as staff_user,
post_id, IP_NTOA, reason, is_cleared from DBPREFIXreports WHERE is_cleared = FALSE`)
if err != nil {
return nil, err
}
defer rows.Close()
reports := make([]map[string]interface{}, 0)
for rows.Next() {
var id int
var staff_id interface{}
var staff_user []byte
var post_id int
var ip string
var reason string
var is_cleared int
err = rows.Scan(&id, &staff_id, &staff_user, &post_id, &ip, &reason, &is_cleared)
if err != nil {
return nil, err
}
post, err := gcsql.GetPostFromID(post_id, true)
if err != nil {
return nil, err
}
staff_id_int, _ := staff_id.(int64)
reports = append(reports, map[string]interface{}{
"id": id,
"staff_id": int(staff_id_int),
"staff_user": string(staff_user),
"post_link": post.WebPath(),
"ip": ip,
"reason": reason,
"is_cleared": is_cleared,
})
}
if wantsJSON {
return reports, err
}
reportsBuffer := bytes.NewBufferString("")
err = serverutil.MinifyTemplate(gctemplates.ManageReports,
map[string]interface{}{
"reports": reports,
"staff": staff,
}, reportsBuffer, "text/html")
if err != nil {
errEv.Err(err).Caller().Send()
return "", err
}
output = reportsBuffer.String()
return
}
func threadAttrsCallback(_ http.ResponseWriter, request *http.Request, _ *gcsql.Staff, wantsJSON bool, infoEv, errEv *zerolog.Event) (output interface{}, err error) {
boardDir := request.FormValue("board")
attrBuffer := bytes.NewBufferString("")
data := map[string]interface{}{
"boards": gcsql.AllBoards,
}
if boardDir == "" {
if wantsJSON {
return nil, errors.New(`missing required field "board"`)
}
if err = serverutil.MinifyTemplate(gctemplates.ManageThreadAttrs, data, attrBuffer, "text/html"); err != nil {
errEv.Err(err).Caller().Send()
return "", err
}
return attrBuffer.String(), nil
}
gcutil.LogStr("board", boardDir, errEv, infoEv)
board, err := gcsql.GetBoardFromDir(boardDir)
if err != nil {
errEv.Err(err).Caller().Send()
return "", err
}
data["board"] = board
topPostStr := request.FormValue("thread")
if topPostStr != "" {
var topPostID int
if topPostID, err = strconv.Atoi(topPostStr); err != nil {
errEv.Err(err).Str("topPostStr", topPostStr).Caller().Send()
return "", err
}
gcutil.LogInt("topPostID", topPostID, errEv, infoEv)
data["topPostID"] = topPostID
var attr string
var newVal bool
var doChange bool // if false, don't bother executing any SQL since nothing will change
thread, err := gcsql.GetPostThread(topPostID)
if err != nil {
errEv.Err(err).Caller().Send()
return "", err
}
if request.FormValue("unlock") != "" {
attr = "locked"
newVal = false
doChange = thread.Locked != newVal
} else if request.FormValue("lock") != "" {
attr = "locked"
newVal = true
doChange = thread.Locked != newVal
} else if request.FormValue("unsticky") != "" {
attr = "stickied"
newVal = false
doChange = thread.Stickied != newVal
} else if request.FormValue("sticky") != "" {
attr = "stickied"
newVal = true
doChange = thread.Stickied != newVal
} else if request.FormValue("unanchor") != "" {
attr = "anchored"
newVal = false
doChange = thread.Anchored != newVal
} else if request.FormValue("anchor") != "" {
attr = "anchored"
newVal = true
doChange = thread.Anchored != newVal
} else if request.FormValue("uncyclical") != "" {
attr = "cyclical"
newVal = false
doChange = thread.Cyclical != newVal
} else if request.FormValue("cyclical") != "" {
attr = "cyclical"
newVal = true
doChange = thread.Cyclical != newVal
}
if attr != "" && doChange {
gcutil.LogStr("attribute", attr, errEv, infoEv)
gcutil.LogBool("attrVal", newVal, errEv, infoEv)
if err = thread.UpdateAttribute(attr, newVal); err != nil {
errEv.Err(err).Caller().Send()
return "", err
}
if err = building.BuildBoardPages(board); err != nil {
return "", err
}
post, err := gcsql.GetPostFromID(topPostID, true)
if err != nil {
errEv.Err(err).Caller().Send()
return "", err
}
if err = building.BuildThreadPages(post); err != nil {
return "", err
}
fmt.Println("Done rebuilding", board.Dir)
}
data["thread"] = thread
}
threads, err := gcsql.GetThreadsWithBoardID(board.ID, true)
if err != nil {
errEv.Err(err).Caller().Send()
return "", err
}
data["threads"] = threads
var threadIDs []interface{}
for i := len(threads) - 1; i >= 0; i-- {
threadIDs = append(threadIDs, threads[i].ID)
}
if wantsJSON {
return threads, nil
}
opMap, err := gcsql.GetTopPostIDsInThreadIDs(threadIDs...)
if err != nil {
errEv.Err(err).Caller().Send()
return "", err
}
data["opMap"] = opMap
var formURL url.URL
formURL.Path = config.WebPath("/manage/threadattrs")
vals := formURL.Query()
vals.Set("board", boardDir)
if topPostStr != "" {
vals.Set("thread", topPostStr)
}
formURL.RawQuery = vals.Encode()
data["formURL"] = formURL.String()
if err = serverutil.MinifyTemplate(gctemplates.ManageThreadAttrs, data, attrBuffer, "text/html"); err != nil {
errEv.Err(err).Caller().Send()
return "", err
}
return attrBuffer.String(), nil
}
type postInfoJSON struct {
Post *gcsql.Post `json:"post"`
FQDN []string `json:"ipFQDN"`
OriginalFilename string `json:"originalFilename,omitempty"`
Checksum string `json:"checksum,omitempty"`
Fingerprint string `json:"fingerprint,omitempty"`
}
func postInfoCallback(_ http.ResponseWriter, request *http.Request, _ *gcsql.Staff, _ bool, _ *zerolog.Event, _ *zerolog.Event) (output interface{}, err error) {
postIDstr := request.FormValue("postid")
if postIDstr == "" {
return "", errors.New("invalid request (missing postid)")
}
var postID int
if postID, err = strconv.Atoi(postIDstr); err != nil {
return "", err
}
post, err := gcsql.GetPostFromID(postID, true)
if err != nil {
return "", err
}
postInfo := postInfoJSON{
Post: post,
}
names, err := net.LookupAddr(post.IP)
if err == nil {
postInfo.FQDN = names
} else {
postInfo.FQDN = []string{err.Error()}
}
upload, err := post.GetUpload()
if err != nil {
return "", err
}
if upload != nil {
postInfo.OriginalFilename = upload.OriginalFilename
postInfo.Checksum = upload.Checksum
postInfo.Fingerprint, err = uploads.GetPostImageFingerprint(postID)
if err != nil {
return "", err
}
}
return postInfo, nil
}
type fingerprintJSON struct {
Fingerprint string `json:"fingerprint"`
}
func fingerprintCallback(_ http.ResponseWriter, request *http.Request, _ *gcsql.Staff, _ bool, _ *zerolog.Event, errEv *zerolog.Event) (output interface{}, err error) {
postIDstr := request.Form.Get("post")
if postIDstr == "" {
return "", errors.New("missing 'post' field")
}
postID, err := strconv.Atoi(postIDstr)
if err != nil {
errEv.Err(err).Caller().Send()
return "", err
}
fingerprint, err := uploads.GetPostImageFingerprint(postID)
if errors.Is(err, sql.ErrNoRows) {
return "", errors.New("post has no files or post doesn't exist")
} else if err != nil {
errEv.Err(err).Caller().Send()
return "", err
}
return fingerprintJSON{
Fingerprint: fingerprint,
}, nil
}
func wordfiltersCallback(_ http.ResponseWriter, request *http.Request, staff *gcsql.Staff, _ bool, infoEv *zerolog.Event, errEv *zerolog.Event) (output interface{}, err error) {
do := request.PostFormValue("dowordfilter")
editIDstr := request.FormValue("edit")
disableIDstr := request.FormValue("disable")
enableIDstr := request.FormValue("enable")
if disableIDstr != "" {
disableID, err := strconv.Atoi(disableIDstr)
if err != nil {
errEv.Err(err).Caller().Str("disableID", disableIDstr).Send()
return nil, err
}
if err = gcsql.SetFilterActive(disableID, false); err != nil {
errEv.Err(err).Caller().Int("disableID", disableID).Msg("Unable to disable filter")
return nil, errors.New("unable to disable wordfilter")
}
infoEv.Int("disableID", disableID)
} else if enableIDstr != "" {
enableID, err := strconv.Atoi(enableIDstr)
if err != nil {
errEv.Err(err).Caller().Str("enableID", enableIDstr).Send()
return nil, err
}
if err = gcsql.SetFilterActive(enableID, true); err != nil {
errEv.Err(err).Caller().Int("enableID", enableID).Msg("Unable to enable filter")
return nil, errors.New("unable to enable wordfilter")
}
infoEv.Int("enableID", enableID)
}
var filter *gcsql.Wordfilter
if editIDstr != "" {
editID, err := strconv.Atoi(editIDstr)
if err != nil {
errEv.Err(err).Str("editID", editIDstr).Send()
return nil, err
}
gcutil.LogInt("editID", editID, infoEv, errEv)
filter, err = gcsql.GetWordfilterByID(editID)
if err != nil {
errEv.Err(err).Caller().Msg("Unable to get wordfilter")
return nil, fmt.Errorf("Unable to get wordfilter with id #%d", editID)
}
}
searchFor := request.PostFormValue("searchfor")
replaceWith := request.PostFormValue("replace")
isRegex := request.PostFormValue("isregex") == "on"
staffNote := request.PostFormValue("staffnote")
var boards []string
boardsLog := zerolog.Arr()
for k, v := range request.PostForm {
if strings.HasPrefix(k, "board-") && v[0] == "on" {
boards = append(boards, k[6:])
boardsLog.Str(k[6:])
}
}
if do != "" {
infoEv.Array("boards", boardsLog)
errEv.Array("boards", boardsLog)
gcutil.LogStr("searchFor", searchFor, infoEv, errEv)
gcutil.LogStr("replaceWith", replaceWith, infoEv, errEv)
gcutil.LogStr("staffnote", staffNote, infoEv, errEv)
gcutil.LogBool("isRegex", isRegex, infoEv, errEv)
}
switch do {
case "Edit wordfilter":
if err = filter.UpdateDetails(staffNote, "replace", replaceWith); err != nil {
errEv.Err(err).Caller().Msg("Unable to update wordfilter details")
return nil, errors.New("unable to update wordfilter details")
}
if err = filter.SetConditions(gcsql.FilterCondition{
FilterID: filter.ID,
IsRegex: isRegex,
Search: searchFor,
Field: "body",
}); err != nil {
errEv.Err(err).Caller().Msg("Unable to set filter condition")
return nil, errors.New("unable to set filter conditions")
}
if err = filter.SetBoardDirs(boards...); err != nil {
errEv.Err(err).Caller().Msg("Unable to set board directories")
return nil, errors.New("unable to set board directories")
}
infoEv.Str("do", "update")
case "Create wordfilter":
if _, err = gcsql.CreateWordFilter(searchFor, replaceWith, isRegex, boards, staff.ID, staffNote); err != nil {
errEv.Err(err).Caller().Msg("Unable to create wordfilter")
return nil, errors.New("unable to create wordfilter")
}
infoEv.Str("do", "create")
}
wordfilters, err := gcsql.GetWordfilters(gcsql.AllFilters)
if err != nil {
errEv.Err(err).Caller().Msg("Unable to get wordfilters")
return nil, err
}
var searchFields []string
for _, wordfilter := range wordfilters {
conditions, err := wordfilter.Conditions()
if err != nil {
return nil, err
}
if err = wordfilter.VerifySingleCondition(conditions); err != nil {
return nil, err
}
searchFields = append(searchFields, conditions[0].Search)
}
var buf bytes.Buffer
if err = serverutil.MinifyTemplate(gctemplates.ManageWordfilters, map[string]any{
"wordfilters": wordfilters,
"filter": filter,
"searchFields": searchFields,
"allBoards": gcsql.AllBoards,
}, &buf, "text/html"); err != nil {
errEv.Err(err).Str("template", "manage_wordfilters.html").Caller().Send()
return nil, err
}
if do != "" || enableIDstr != "" || disableIDstr != "" {
infoEv.Send()
}
return buf.String(), nil
}
func registerModeratorPages() {
actions = append(actions,
Action{
ID: "bans",
Title: "Bans",
Permissions: ModPerms,
Callback: bansCallback,
},
Action{
ID: "appeals",
Title: "Ban appeals",
Permissions: ModPerms,
JSONoutput: OptionalJSON,
Callback: appealsCallback,
},
Action{
ID: "filters",
Title: "Post filters",
Permissions: ModPerms,
JSONoutput: NoJSON,
Callback: filtersCallback,
},
// Action{
// ID: "filebans",
// Title: "Filename and checksum bans",
// Permissions: ModPerms,
// JSONoutput: OptionalJSON,
// Callback: fileBansCallback,
// },
// Action{
// ID: "namebans",
// Title: "Name bans",
// Permissions: ModPerms,
// Callback: nameBansCallback,
// },
Action{
ID: "ipsearch",
Title: "IP Search",
Permissions: ModPerms,
JSONoutput: NoJSON,
Callback: ipSearchCallback,
},
Action{
ID: "reports",
Title: "Reports",
Permissions: ModPerms,
JSONoutput: OptionalJSON,
Callback: reportsCallback,
},
Action{
ID: "threadattrs",
Title: "View/Update Thread Attributes",
Permissions: ModPerms,
JSONoutput: OptionalJSON,
Callback: threadAttrsCallback,
},
Action{
ID: "postinfo",
Title: "Post info",
Permissions: ModPerms,
JSONoutput: AlwaysJSON,
Callback: postInfoCallback,
},
Action{
ID: "fingerprint",
Title: "Get image/thumbnail fingerprint",
Permissions: ModPerms,
JSONoutput: AlwaysJSON,
Callback: fingerprintCallback,
},
Action{
ID: "wordfilters",
Title: "Wordfilters",
Permissions: ModPerms,
Callback: wordfiltersCallback,
},
)
}