mirror of
https://github.com/Eggbertx/gochan.git
synced 2025-08-03 11:46:22 -07:00
(Mostly) finish the configuration web interface, remove all _img fields since text boards won't happen
This commit is contained in:
parent
0ef760dd61
commit
c700e4c0ce
20 changed files with 415 additions and 379 deletions
|
@ -34,25 +34,17 @@
|
|||
"SiteWebfolder": "/",
|
||||
"DomainRegex": "(https|http):\\/\\/(gochan\\.lunachan\\.net|gochan\\.org)\\/(.*)",
|
||||
|
||||
"Styles_img": ["pipes","efchan"],
|
||||
"DefaultStyle_img": "pipes",
|
||||
"Styles_txt": ["efchan,pipes"],
|
||||
"DefaultStyle_txt": "pipes",
|
||||
"Styles": ["pipes","efchan"],
|
||||
"DefaultStyle": "pipes",
|
||||
|
||||
"AllowDuplicateImages": true,
|
||||
"AllowVideoUploads": true,
|
||||
"NewThreadDelay": 30,
|
||||
"ReplyDelay": 7,
|
||||
"MaxLineLength": 150,
|
||||
"ReversedTrips": [
|
||||
{
|
||||
"from": "##thischangesto",
|
||||
"to": "this"
|
||||
},
|
||||
{
|
||||
"from": "##andthischangesto",
|
||||
"to": "this"
|
||||
}
|
||||
"ReservedTrips": [
|
||||
"thischangesto##this",
|
||||
"andthischangesto##this"
|
||||
],
|
||||
|
||||
"ThumbWidth": 200,
|
||||
|
@ -62,18 +54,15 @@
|
|||
"ThumbWidth_catalog": 50,
|
||||
"ThumbHeight_catalog": 50,
|
||||
|
||||
"ThreadsPerPage_img": 4,
|
||||
"ThreadsPerPage_txt": 15,
|
||||
"PostsPerThreadPage": 4,
|
||||
"ThreadsPerPage": 15,
|
||||
"PostsPerThreadPage": 50,
|
||||
"RepliesOnBoardPage": 3,
|
||||
"StickyRepliesOnBoardPage": 1,
|
||||
"BanColors": [
|
||||
{
|
||||
"Role": "admin",
|
||||
"Color": "#0000A0"
|
||||
}
|
||||
"admin:#0000A0",
|
||||
"somemod:blue"
|
||||
],
|
||||
"BanMsg": "<br /><span style=\"color:##BANCOLOR## \"><b>(USER WAS BANNED FOR THIS POST)</b></span>",
|
||||
"BanMsg": "USER WAS BANNED FOR THIS POST",
|
||||
"EmbedWidth": 200,
|
||||
"EmbedHeight": 164,
|
||||
"ExpandButton": true,
|
||||
|
@ -81,9 +70,7 @@
|
|||
"MakeURLsHyperlinked": true,
|
||||
"NewTabOnOutlinks": true,
|
||||
|
||||
|
||||
"DateTimeFormat": "Mon, January 02, 2006 15:04 PM",
|
||||
"DefaultBanReason": "",
|
||||
"AkismetAPIKey": "",
|
||||
"EnableGeoIP": true,
|
||||
"_comment": "set GeoIPDBlocation to cf to use Cloudflare's GeoIP",
|
||||
|
@ -91,7 +78,7 @@
|
|||
"MaxRecentPosts": 3,
|
||||
"Verbosity": 0,
|
||||
"EnableAppeals": true,
|
||||
"MaxModlogDays": 14,
|
||||
"MaxLogDays": 14,
|
||||
"_comment": "Set RandomSeed to a (preferrably large) string of letters and numbers",
|
||||
"RandomSeed": ""
|
||||
}
|
||||
|
|
|
@ -58,4 +58,18 @@ table, table th, td, tr {
|
|||
h1, h2 {
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
input.config-text {
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
padding: 4px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.warning, div.config-status {
|
||||
color:red;
|
||||
font-weight: bold;
|
||||
}
|
|
@ -1,175 +0,0 @@
|
|||
#main {
|
||||
border-bottom:10px!important;
|
||||
height:85%;
|
||||
left:16%;
|
||||
position:absolute;
|
||||
top:90px;
|
||||
width:auto;
|
||||
}
|
||||
|
||||
#menu {
|
||||
border:0;
|
||||
height:100%;
|
||||
left:0;
|
||||
margin-left:5px;
|
||||
padding:0;
|
||||
position:absolute;
|
||||
top:75px;
|
||||
width:15%;
|
||||
}
|
||||
|
||||
#site-title {
|
||||
font-size:50px;
|
||||
}
|
||||
|
||||
#top-pane {
|
||||
height:75px;
|
||||
left:0;
|
||||
position:absolute;
|
||||
text-align:center;
|
||||
top:0;
|
||||
width:100%;
|
||||
}
|
||||
|
||||
#topmenu {
|
||||
left:16%;
|
||||
position:absolute;
|
||||
top:76px;
|
||||
}
|
||||
|
||||
#topmenu li {
|
||||
background-color:#DFDFFE;
|
||||
border:1px solid #9295a4;
|
||||
border-left:none;
|
||||
display:block;
|
||||
float:left;
|
||||
margin-top:-7px;
|
||||
padding:3px 10px 2px;
|
||||
}
|
||||
|
||||
#topmenu li.current {
|
||||
background-color:#D6DAF0;
|
||||
border-bottom:none;
|
||||
margin-top:-8px;
|
||||
padding-top:5px;
|
||||
}
|
||||
|
||||
#topmenu li.first {
|
||||
border-left:1px solid #9295a4;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-left:0!important;
|
||||
padding-left:0!important;
|
||||
text-align:justify;
|
||||
}
|
||||
|
||||
.menu {
|
||||
margin-top:1em;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
.newssub {
|
||||
position:absolute;
|
||||
}
|
||||
|
||||
.permalink {
|
||||
display:block;
|
||||
text-align:right;
|
||||
}
|
||||
|
||||
.permalink a {
|
||||
color:blue;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
.plus {
|
||||
background:#c5c9e0;
|
||||
border:1px solid #b4b8d0;
|
||||
color:#000;
|
||||
cursor:pointer;
|
||||
float:right;
|
||||
font-size:8px;
|
||||
font-weight:400;
|
||||
margin:0;
|
||||
padding:1px 4px 2px;
|
||||
}
|
||||
|
||||
.plus:hover {
|
||||
background:#c5c9e0;
|
||||
border:1px solid #c97;
|
||||
}
|
||||
|
||||
a {
|
||||
color:#34345C;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
body {
|
||||
background:#EEF2FF;
|
||||
color:#000;
|
||||
font-family:sans-serif;
|
||||
font-size:75%;
|
||||
margin:8px;
|
||||
width:90%;
|
||||
}
|
||||
|
||||
body,html {
|
||||
height:100%;
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color:#000;
|
||||
font-size:150%;
|
||||
margin:0;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
h1,h2 {
|
||||
background:#D6DAF0;
|
||||
text-align:left;
|
||||
}
|
||||
|
||||
h1,h3,.menu {
|
||||
font-family:Verdana,Tahoma,sans-serif;
|
||||
}
|
||||
|
||||
h2 {
|
||||
background:#D6DAF0;
|
||||
font-size:100%;
|
||||
margin:1em 0 0;
|
||||
}
|
||||
|
||||
h2 a {
|
||||
color:#550;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color:#800;
|
||||
font-size:medium;
|
||||
font-weight:400;
|
||||
margin:0;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
li {
|
||||
margin:0;
|
||||
}
|
||||
|
||||
li a {
|
||||
display:block;
|
||||
width:100%;
|
||||
}
|
||||
|
||||
ul.boardmenu li:hover,ul.modmenulink li:hover {
|
||||
background:#C6DAF0;
|
||||
}
|
||||
|
||||
ul.boardmenu,ul.modmenulink,div#topmenu ul {
|
||||
list-style:none;
|
||||
margin:0;
|
||||
padding-left:0;
|
||||
}
|
|
@ -4,7 +4,6 @@ import (
|
|||
"os"
|
||||
)
|
||||
|
||||
// set in Makefile via -ldflags
|
||||
var version string
|
||||
var buildtimeString string // set in Makefile, format: YRMMDD.HHMM
|
||||
|
||||
|
|
215
src/manage.go
215
src/manage.go
|
@ -3,12 +3,15 @@ package main
|
|||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
@ -124,7 +127,7 @@ func createSession(key string, username string, password string, request *http.R
|
|||
//returns 0 for successful, 1 for password mismatch, and 2 for other
|
||||
domain := request.Host
|
||||
var err error
|
||||
chopPortNumRegex := regexp.MustCompile("(.+|\\w+):(\\d+)$")
|
||||
chopPortNumRegex := regexp.MustCompile(`(.+|\w+):(\d+)$`)
|
||||
domain = chopPortNumRegex.Split(domain, -1)[0]
|
||||
|
||||
if !validReferrer(request) {
|
||||
|
@ -208,22 +211,205 @@ var manage_functions = map[string]ManageFunction{
|
|||
Permissions: 3,
|
||||
Callback: func(writer http.ResponseWriter, request *http.Request) (html string) {
|
||||
do := request.FormValue("do")
|
||||
var status string
|
||||
if do == "save" {
|
||||
// configJSON, err := json.Marshal(config)
|
||||
// if err != nil {
|
||||
// html += err.Error()
|
||||
// return
|
||||
// }
|
||||
configJSON, err := json.MarshalIndent(config, "", "\t")
|
||||
if err != nil {
|
||||
status += err.Error() + "<br />\n"
|
||||
} else if err = ioutil.WriteFile("gochan.json", configJSON, 0777); err != nil {
|
||||
status += "Error backing up old gochan.json, cancelling save: " + err.Error() + "\n"
|
||||
} else {
|
||||
config.Lockdown = (request.PostFormValue("Lockdown") == "on")
|
||||
config.LockdownMessage = request.PostFormValue("LockdownMessage")
|
||||
Sillytags_arr := strings.Split(request.PostFormValue("Sillytags"), "\n")
|
||||
var Sillytags []string
|
||||
for _, tag := range Sillytags_arr {
|
||||
Sillytags = append(Sillytags, strings.Trim(tag, " \n\r"))
|
||||
}
|
||||
config.Sillytags = Sillytags
|
||||
config.UseSillytags = (request.PostFormValue("UseSillytags") == "on")
|
||||
config.Modboard = request.PostFormValue("Modboard")
|
||||
config.SiteName = request.PostFormValue("SiteName")
|
||||
config.SiteSlogan = request.PostFormValue("SiteSlogan")
|
||||
config.SiteHeaderURL = request.PostFormValue("SiteHeaderURL")
|
||||
config.SiteWebfolder = request.PostFormValue("SiteWebfolder")
|
||||
Styles_arr := strings.Split(request.PostFormValue("Styles"), "\n")
|
||||
var Styles []string
|
||||
for _, style := range Styles_arr {
|
||||
Styles = append(Styles, strings.Trim(style, " \n\r"))
|
||||
}
|
||||
config.Styles = Styles
|
||||
config.DefaultStyle = request.PostFormValue("DefaultStyle")
|
||||
config.AllowDuplicateImages = (request.PostFormValue("AllowDuplicateImages") == "on")
|
||||
config.AllowVideoUploads = (request.PostFormValue("AllowVideoUploads") == "on")
|
||||
NewThreadDelay, err := strconv.Atoi(request.PostFormValue("NewThreadDelay"))
|
||||
if err != nil {
|
||||
status += err.Error() + "<br />\n"
|
||||
} else {
|
||||
config.NewThreadDelay = NewThreadDelay
|
||||
}
|
||||
|
||||
// err = ioutil.WriteFile("gochan.json", configJSON, 0666)
|
||||
// if err != nil {
|
||||
// html += "Error writing \"gochan.json\": %s\n" + err.Error()
|
||||
// return
|
||||
// }
|
||||
ReplyDelay, err := strconv.Atoi(request.PostFormValue("ReplyDelay"))
|
||||
if err != nil {
|
||||
status += err.Error() + "<br />\n"
|
||||
} else {
|
||||
config.ReplyDelay = ReplyDelay
|
||||
}
|
||||
|
||||
MaxLineLength, err := strconv.Atoi(request.PostFormValue("MaxLineLength"))
|
||||
if err != nil {
|
||||
status += err.Error() + "<br />\n"
|
||||
} else {
|
||||
config.MaxLineLength = MaxLineLength
|
||||
}
|
||||
|
||||
ReservedTrips_arr := strings.Split(request.PostFormValue("ReservedTrips"), "\n")
|
||||
var ReservedTrips []string
|
||||
for _, trip := range ReservedTrips_arr {
|
||||
ReservedTrips = append(ReservedTrips, strings.Trim(trip, " \n\r"))
|
||||
|
||||
}
|
||||
config.ReservedTrips = ReservedTrips
|
||||
|
||||
ThumbWidth, err := strconv.Atoi(request.PostFormValue("ThumbWidth"))
|
||||
if err != nil {
|
||||
status += err.Error() + "<br />\n"
|
||||
} else {
|
||||
config.ThumbWidth = ThumbWidth
|
||||
}
|
||||
|
||||
ThumbHeight, err := strconv.Atoi(request.PostFormValue("ThumbHeight"))
|
||||
if err != nil {
|
||||
status += err.Error() + "<br />\n"
|
||||
} else {
|
||||
config.ThumbHeight = ThumbHeight
|
||||
}
|
||||
|
||||
ThumbWidth_reply, err := strconv.Atoi(request.PostFormValue("ThumbWidth_reply"))
|
||||
if err != nil {
|
||||
status += err.Error() + "<br />\n"
|
||||
} else {
|
||||
config.ThumbWidth_reply = ThumbWidth_reply
|
||||
}
|
||||
|
||||
ThumbHeight_reply, err := strconv.Atoi(request.PostFormValue("ThumbHeight_reply"))
|
||||
if err != nil {
|
||||
status += err.Error() + "<br />\n"
|
||||
} else {
|
||||
config.ThumbHeight_reply = ThumbHeight_reply
|
||||
}
|
||||
|
||||
ThumbWidth_catalog, err := strconv.Atoi(request.PostFormValue("ThumbWidth_catalog"))
|
||||
if err != nil {
|
||||
status += err.Error() + "<br />\n"
|
||||
} else {
|
||||
config.ThumbWidth_catalog = ThumbWidth_catalog
|
||||
}
|
||||
|
||||
ThumbHeight_catalog, err := strconv.Atoi(request.PostFormValue("ThumbHeight_catalog"))
|
||||
if err != nil {
|
||||
status += err.Error() + "<br />\n"
|
||||
} else {
|
||||
config.ThumbHeight_catalog = ThumbHeight_catalog
|
||||
}
|
||||
|
||||
PostsPerThreadPage, err := strconv.Atoi(request.PostFormValue("PostsPerThreadPage"))
|
||||
if err != nil {
|
||||
status += err.Error() + "<br />\n"
|
||||
} else {
|
||||
config.PostsPerThreadPage = PostsPerThreadPage
|
||||
}
|
||||
|
||||
RepliesOnBoardPage, err := strconv.Atoi(request.PostFormValue("RepliesOnBoardPage"))
|
||||
if err != nil {
|
||||
status += err.Error() + "<br />\n"
|
||||
} else {
|
||||
config.RepliesOnBoardPage = RepliesOnBoardPage
|
||||
}
|
||||
|
||||
StickyRepliesOnBoardPage, err := strconv.Atoi(request.PostFormValue("StickyRepliesOnBoardPage"))
|
||||
if err != nil {
|
||||
status += err.Error() + "<br />\n"
|
||||
} else {
|
||||
config.StickyRepliesOnBoardPage = StickyRepliesOnBoardPage
|
||||
}
|
||||
|
||||
BanColors_arr := strings.Split(request.PostFormValue("BanColors"), "\n")
|
||||
var BanColors []string
|
||||
for _, color := range BanColors_arr {
|
||||
BanColors = append(BanColors, strings.Trim(color, " \n\r"))
|
||||
|
||||
}
|
||||
config.BanColors = BanColors
|
||||
|
||||
config.BanMsg = request.PostFormValue("BanMsg")
|
||||
EmbedWidth, err := strconv.Atoi(request.PostFormValue("EmbedWidth"))
|
||||
if err != nil {
|
||||
status += err.Error() + "<br />\n"
|
||||
} else {
|
||||
config.EmbedWidth = EmbedWidth
|
||||
}
|
||||
|
||||
EmbedHeight, err := strconv.Atoi(request.PostFormValue("EmbedHeight"))
|
||||
if err != nil {
|
||||
status += err.Error() + "<br />\n"
|
||||
} else {
|
||||
config.EmbedHeight = EmbedHeight
|
||||
}
|
||||
|
||||
config.ExpandButton = (request.PostFormValue("ExpandButton") == "on")
|
||||
config.ImagesOpenNewTab = (request.PostFormValue("ImagesOpenNewTab") == "on")
|
||||
config.MakeURLsHyperlinked = (request.PostFormValue("MakeURLsHyperlinked") == "on")
|
||||
config.NewTabOnOutlinks = (request.PostFormValue("NewTabOnOutlinks") == "on")
|
||||
config.EnableQuickReply = (request.PostFormValue("EnableQuickReply") == "on")
|
||||
config.DateTimeFormat = request.PostFormValue("DateTimeFormat")
|
||||
AkismetAPIKey := request.PostFormValue("AkismetAPIKey")
|
||||
err = checkAkismetAPIKey(AkismetAPIKey)
|
||||
if err != nil {
|
||||
status += err.Error() + "<br />"
|
||||
} else {
|
||||
config.AkismetAPIKey = AkismetAPIKey
|
||||
}
|
||||
|
||||
config.EnableGeoIP = (request.PostFormValue("EnableGeoIP") == "on")
|
||||
config.GeoIPDBlocation = request.PostFormValue("GeoIPDBlocation")
|
||||
|
||||
MaxRecentPosts, err := strconv.Atoi(request.PostFormValue("MaxRecentPosts"))
|
||||
if err != nil {
|
||||
status += err.Error() + "<br />\n"
|
||||
} else {
|
||||
config.MaxRecentPosts = MaxRecentPosts
|
||||
}
|
||||
|
||||
Verbosity, err := strconv.Atoi(request.PostFormValue("Verbosity"))
|
||||
if err != nil {
|
||||
status += err.Error() + "<br />\n"
|
||||
} else {
|
||||
config.Verbosity = Verbosity
|
||||
}
|
||||
|
||||
config.EnableAppeals = (request.PostFormValue("EnableAppeals") == "on")
|
||||
MaxLogDays, err := strconv.Atoi(request.PostFormValue("MaxLogDays"))
|
||||
if err != nil {
|
||||
status += err.Error() + "<br />\n"
|
||||
} else {
|
||||
config.MaxLogDays = MaxLogDays
|
||||
}
|
||||
|
||||
configJSON, err = json.MarshalIndent(config, "", "\t")
|
||||
if err != nil {
|
||||
status += err.Error() + "<br />\n"
|
||||
} else if err = ioutil.WriteFile("gochan.json", configJSON, 0777); err != nil {
|
||||
status = "Error writing gochan.json: %s\n" + err.Error()
|
||||
} else {
|
||||
status = "Wrote gochan.json successfully <br />"
|
||||
}
|
||||
}
|
||||
}
|
||||
manageConfigBuffer := bytes.NewBufferString("")
|
||||
|
||||
if err := manage_config_tmpl.Execute(manageConfigBuffer, nil); err != nil {
|
||||
if err := manage_config_tmpl.Execute(manageConfigBuffer,
|
||||
map[string]interface{}{"config": config, "status": status},
|
||||
); err != nil {
|
||||
html += handleError(1, err.Error())
|
||||
return
|
||||
}
|
||||
|
@ -576,7 +762,8 @@ var manage_functions = map[string]ManageFunction{
|
|||
if err != nil {
|
||||
board.MaxPages = 11
|
||||
}
|
||||
board.DefaultStyle = request.FormValue("defaultstyle")
|
||||
|
||||
board.DefaultStyle = strings.Trim(request.FormValue("defaultstyle"), "\n")
|
||||
board.Locked = (request.FormValue("locked") == "on")
|
||||
board.ForcedAnon = (request.FormValue("forcedanon") == "on")
|
||||
|
||||
|
|
|
@ -112,7 +112,6 @@ func buildBoardPages(board *BoardsTable) (html string) {
|
|||
for _, op := range op_posts {
|
||||
var thread Thread
|
||||
var posts_in_thread []PostTable
|
||||
thread.IName = "thread"
|
||||
|
||||
// Get the number of replies to this thread.
|
||||
if err = queryRowSQL("SELECT COUNT(*) FROM `"+config.DBprefix+"posts` WHERE `boardid` = ? AND `parentid` = ? AND `deleted_timestamp` = ?",
|
||||
|
@ -214,7 +213,7 @@ func buildBoardPages(board *BoardsTable) (html string) {
|
|||
return
|
||||
} else {
|
||||
// Create the archive pages.
|
||||
thread_pages = paginate(config.ThreadsPerPage_img, threads)
|
||||
thread_pages = paginate(config.ThreadsPerPage, threads)
|
||||
board.NumPages = len(thread_pages) - 1
|
||||
|
||||
// Create array of page wrapper objects, and open the file.
|
||||
|
@ -475,7 +474,6 @@ func buildFrontPage() (html string) {
|
|||
|
||||
for rows.Next() {
|
||||
frontpage := new(FrontTable)
|
||||
frontpage.IName = "front page"
|
||||
if err = rows.Scan(&frontpage.ID, &frontpage.Page, &frontpage.Order, &frontpage.Subject,
|
||||
&frontpage.Message, &frontpage.Timestamp, &frontpage.Poster, &frontpage.Email); err != nil {
|
||||
return handleError(1, err.Error())
|
||||
|
@ -537,7 +535,7 @@ func buildBoardListJSON() (html string) {
|
|||
for _, board_int := range allBoards {
|
||||
board := board_int.(BoardsTable)
|
||||
board_obj := BoardJSON{BoardName: board.Dir, Title: board.Title, WorkSafeBoard: 1,
|
||||
ThreadsPerPage: config.ThreadsPerPage_img, Pages: board.MaxPages, MaxFilesize: board.MaxImageSize,
|
||||
ThreadsPerPage: config.ThreadsPerPage, Pages: board.MaxPages, MaxFilesize: board.MaxImageSize,
|
||||
MaxMessageLength: board.MaxMessageLength, BumpLimit: 200, ImageLimit: board.NoImagesAfter,
|
||||
Cooldowns: cooldowns_obj, Description: board.Description, IsArchived: 0}
|
||||
if board.EnableNSFW {
|
||||
|
@ -568,14 +566,13 @@ func bumpThread(postID, boardID int) error {
|
|||
// Checks check poster's name/tripcode/file checksum (from PostTable post) for banned status
|
||||
// returns true if the user is banned
|
||||
func checkBannedStatus(post *PostTable, writer http.ResponseWriter) ([]interface{}, error) {
|
||||
var isExpired bool
|
||||
var ban_entry BanlistTable
|
||||
var banEntry BanlistTable
|
||||
var interfaces []interface{}
|
||||
// var count int
|
||||
// var search string
|
||||
err := queryRowSQL("SELECT `ip`, `name`, `tripcode`, `message`, `boards`, `timestamp`, `expires`, `appeal_at` FROM `"+config.DBprefix+"banlist` WHERE `ip` = ?",
|
||||
[]interface{}{&post.IP},
|
||||
[]interface{}{&ban_entry.IP, &ban_entry.Name, &ban_entry.Tripcode, &ban_entry.Message, &ban_entry.Boards, &ban_entry.Timestamp, &ban_entry.Expires, &ban_entry.AppealAt},
|
||||
[]interface{}{&banEntry.IP, &banEntry.Name, &banEntry.Tripcode, &banEntry.Message, &banEntry.Boards, &banEntry.Timestamp, &banEntry.Expires, &banEntry.AppealAt},
|
||||
)
|
||||
if err == sql.ErrNoRows {
|
||||
// the user isn't banned
|
||||
|
@ -585,18 +582,18 @@ func checkBannedStatus(post *PostTable, writer http.ResponseWriter) ([]interface
|
|||
handleError(1, "Error checking banned status: "+err.Error())
|
||||
return interfaces, err
|
||||
}
|
||||
isExpired = ban_entry.Expires.After(time.Now()) == false
|
||||
if isExpired {
|
||||
|
||||
if !banEntry.Expires.After(time.Now()) {
|
||||
// if it is expired, send a message saying that it's expired, but still post
|
||||
println(1, "expired")
|
||||
return interfaces, nil
|
||||
}
|
||||
// the user's IP is in the banlist. Check if the ban has expired
|
||||
if getSpecificSQLDateTime(ban_entry.Expires) == "0001-01-01 00:00:00" || ban_entry.Expires.After(time.Now()) {
|
||||
if getSpecificSQLDateTime(banEntry.Expires) == "0001-01-01 00:00:00" || banEntry.Expires.After(time.Now()) {
|
||||
// for some funky reason, Go's MySQL driver seems to not like getting a supposedly nil timestamp as an ACTUAL nil timestamp
|
||||
// so we're just going to wing it and cheat. Of course if they change that, we're kind of hosed.
|
||||
|
||||
return []interface{}{config, ban_entry}, nil
|
||||
return []interface{}{config, banEntry}, nil
|
||||
}
|
||||
return interfaces, nil
|
||||
}
|
||||
|
@ -633,8 +630,8 @@ func createImageThumbnail(image_obj image.Image, size string) image.Image {
|
|||
return image_obj
|
||||
}
|
||||
|
||||
thumb_w, thumb_h := getThumbnailSize(old_rect.Max.X, old_rect.Max.Y, size)
|
||||
image_obj = imaging.Resize(image_obj, thumb_w, thumb_h, imaging.CatmullRom) // resize to 600x400 px using CatmullRom cubic filter
|
||||
thumbW, thumbH := getThumbnailSize(old_rect.Max.X, old_rect.Max.Y, size)
|
||||
image_obj = imaging.Resize(image_obj, thumbW, thumbH, imaging.CatmullRom) // resize to 600x400 px using CatmullRom cubic filter
|
||||
return image_obj
|
||||
}
|
||||
|
||||
|
@ -680,7 +677,7 @@ func getNewFilename() string {
|
|||
}
|
||||
|
||||
// find out what out thumbnail's width and height should be, partially ripped from Kusaba X
|
||||
func getThumbnailSize(w int, h int, size string) (new_w int, new_h int) {
|
||||
func getThumbnailSize(w int, h int, size string) (newWidth int, newHeight int) {
|
||||
var thumbWidth int
|
||||
var thumbHeight int
|
||||
|
||||
|
@ -696,8 +693,8 @@ func getThumbnailSize(w int, h int, size string) (new_w int, new_h int) {
|
|||
thumbHeight = config.ThumbHeight_catalog
|
||||
}
|
||||
if w == h {
|
||||
new_w = thumbWidth
|
||||
new_h = thumbHeight
|
||||
newWidth = thumbWidth
|
||||
newHeight = thumbHeight
|
||||
} else {
|
||||
var percent float32
|
||||
if w > h {
|
||||
|
@ -705,8 +702,8 @@ func getThumbnailSize(w int, h int, size string) (new_w int, new_h int) {
|
|||
} else {
|
||||
percent = float32(thumbHeight) / float32(h)
|
||||
}
|
||||
new_w = int(float32(w) * percent)
|
||||
new_h = int(float32(h) * percent)
|
||||
newWidth = int(float32(w) * percent)
|
||||
newHeight = int(float32(h) * percent)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -729,7 +726,7 @@ func parseName(name string) map[string]string {
|
|||
// inserts prepared post object into the SQL table so that it can be rendered
|
||||
func insertPost(post PostTable, bump bool) (sql.Result, error) {
|
||||
result, err := execSQL(
|
||||
"INSERT INTO "+config.DBprefix+"posts "+
|
||||
"INSERT INTO `"+config.DBprefix+"posts` "+
|
||||
"(`boardid`,`parentid`,`name`,`tripcode`,`email`,`subject`,`message`,`message_raw`,`password`,`filename`,`filename_original`,`file_checksum`,`filesize`,`image_w`,`image_h`,`thumb_w`,`thumb_h`,`ip`,`tag`,`timestamp`,`autosage`,`poster_authority`,`deleted_timestamp`,`bumped`,`stickied`,`locked`,`reviewed`,`sillytag`)"+
|
||||
"VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||
post.BoardID, post.ParentID, post.Name, post.Tripcode, post.Email,
|
||||
|
@ -768,7 +765,6 @@ func makePost(writer http.ResponseWriter, request *http.Request) {
|
|||
chopPortNumRegex := regexp.MustCompile("(.+|\\w+):(\\d+)$")
|
||||
domain = chopPortNumRegex.Split(domain, -1)[0]
|
||||
|
||||
post.IName = "post"
|
||||
post.ParentID, _ = strconv.Atoi(request.FormValue("threadid"))
|
||||
post.BoardID, _ = strconv.Atoi(request.FormValue("boardid"))
|
||||
|
||||
|
|
|
@ -131,23 +131,23 @@ func initServer() {
|
|||
server.namespaces = make(map[string]func(http.ResponseWriter, *http.Request))
|
||||
|
||||
// Check if Akismet API key is usable at startup.
|
||||
if config.AkismetAPIKey != "" {
|
||||
checkAkismetAPIKey()
|
||||
if err = checkAkismetAPIKey(config.AkismetAPIKey); err != nil {
|
||||
config.AkismetAPIKey = ""
|
||||
handleError(0, "%s", err.Error())
|
||||
}
|
||||
|
||||
// Compile regex for checking referrers.
|
||||
referrerRegex = regexp.MustCompile(config.DomainRegex)
|
||||
|
||||
testfunc := func(writer http.ResponseWriter, request *http.Request) {
|
||||
if writer != nil {
|
||||
_, _ = writer.Write([]byte("hahahaha"))
|
||||
}
|
||||
}
|
||||
|
||||
server.AddNamespace("example", testfunc)
|
||||
server.AddNamespace("manage", callManageFunction)
|
||||
server.AddNamespace("post", makePost)
|
||||
server.AddNamespace("util", utilHandler)
|
||||
server.AddNamespace("example", func(writer http.ResponseWriter, request *http.Request) {
|
||||
if writer != nil {
|
||||
_, _ = writer.Write([]byte("hahahaha"))
|
||||
}
|
||||
})
|
||||
|
||||
// eventually plugins will be able to register new namespaces. Or they will be restricted to something like /plugin
|
||||
|
||||
if config.UseFastCGI {
|
||||
|
|
|
@ -69,7 +69,7 @@ func connectToSQLServer() {
|
|||
}
|
||||
|
||||
if newInstall {
|
||||
printf(0, "\nThis looks like a new install, setting up the database...")
|
||||
printf(0, "\nThis looks like a new install or one that needs updating, setting up the database...")
|
||||
if _, err = db.Exec(
|
||||
"INSERT INTO `" + config.DBname + "`.`" + config.DBprefix + "staff` " +
|
||||
"(`username`, `password_checksum`, `salt`, `rank`) " +
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"html"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
@ -106,8 +107,9 @@ var funcMap = template.FuncMap{
|
|||
return a == b
|
||||
},
|
||||
"intToString": strconv.Itoa,
|
||||
"isStyleDefault_img": func(style string) bool {
|
||||
return style == config.DefaultStyle_img
|
||||
"arrToString": arrToString,
|
||||
"isStyleDefault": func(style string) bool {
|
||||
return style == config.DefaultStyle
|
||||
},
|
||||
"formatTimestamp": humanReadableTime,
|
||||
"getThreadID": func(post_i interface{}) (thread int) {
|
||||
|
@ -152,12 +154,10 @@ var funcMap = template.FuncMap{
|
|||
"formatFilesize": func(size_int int) string {
|
||||
size := float32(size_int)
|
||||
if size < 1000 {
|
||||
return fmt.Sprintf("%fB", size)
|
||||
return fmt.Sprintf("%d B", size_int)
|
||||
} else if size <= 100000 {
|
||||
//size = size * 0.2
|
||||
return fmt.Sprintf("%0.1f KB", size/1024)
|
||||
} else if size <= 100000000 {
|
||||
//size = size * 0.2
|
||||
return fmt.Sprintf("%0.2f MB", size/1024/1024)
|
||||
}
|
||||
return fmt.Sprintf("%0.2f GB", size/1024/1024/1024)
|
||||
|
@ -173,6 +173,58 @@ var funcMap = template.FuncMap{
|
|||
}
|
||||
return img[0:index] + "t." + filetype
|
||||
},
|
||||
"generateConfigTable": func() string {
|
||||
configType := reflect.TypeOf(config)
|
||||
tableOut := "<table style=\"border-collapse: collapse;\"><tr><th>Field name</th><th>Value</th><th>Type</th><th>Description</th></tr>\n"
|
||||
numFields := configType.NumField()
|
||||
for f := 17; f < numFields-2; f++ {
|
||||
// starting at Lockdown because the earlier fields can't be safely edited from a web interface
|
||||
field := configType.Field(f)
|
||||
if field.Tag.Get("critical") != "" {
|
||||
continue
|
||||
}
|
||||
name := field.Name
|
||||
tableOut += "<tr><th>" + name + "</th><td>"
|
||||
f := reflect.Indirect(reflect.ValueOf(config)).FieldByName(name)
|
||||
|
||||
kind := f.Kind()
|
||||
switch kind {
|
||||
case reflect.Int:
|
||||
tableOut += "<input name=\"" + name + "\" type=\"number\" value=\"" + html.EscapeString(fmt.Sprintf("%v", f)) + "\" class=\"config-text\"/>"
|
||||
case reflect.String:
|
||||
tableOut += "<input name=\"" + name + "\" type=\"text\" value=\"" + html.EscapeString(fmt.Sprintf("%v", f)) + "\" class=\"config-text\"/>"
|
||||
case reflect.Bool:
|
||||
checked := ""
|
||||
if f.Bool() {
|
||||
checked = "checked"
|
||||
}
|
||||
tableOut += "<input name=\"" + name + "\" type=\"checkbox\" " + checked + " />"
|
||||
case reflect.Slice:
|
||||
tableOut += "<textarea name=\"" + name + "\" rows=\"4\" cols=\"28\">"
|
||||
arrLength := f.Len()
|
||||
for s := 0; s < arrLength; s++ {
|
||||
newLine := "\n"
|
||||
if s == arrLength-1 {
|
||||
newLine = ""
|
||||
}
|
||||
tableOut += html.EscapeString(f.Slice(s, s+1).Index(0).String()) + newLine
|
||||
}
|
||||
tableOut += "</textarea>"
|
||||
default:
|
||||
tableOut += fmt.Sprintf("%v", kind)
|
||||
}
|
||||
tableOut += "</td><td>" + kind.String() + "</td><td>"
|
||||
defaultTag := field.Tag.Get("default")
|
||||
var defaultTagHTML string
|
||||
if defaultTag != "" {
|
||||
defaultTagHTML = " <b>Default: " + defaultTag + "</b>"
|
||||
}
|
||||
tableOut += field.Tag.Get("description") + defaultTagHTML + "</td>"
|
||||
tableOut += "</tr>\n"
|
||||
}
|
||||
tableOut += "</table>\n"
|
||||
return tableOut
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
136
src/types.go
136
src/types.go
|
@ -36,7 +36,6 @@ type RecentPost struct {
|
|||
}
|
||||
|
||||
type Thread struct {
|
||||
IName string
|
||||
OP PostTable
|
||||
NumReplies int
|
||||
NumImages int
|
||||
|
@ -81,7 +80,6 @@ type BannedHashesTable struct {
|
|||
}
|
||||
|
||||
type BoardsTable struct {
|
||||
IName string
|
||||
ID int
|
||||
CurrentPage int
|
||||
NumPages int
|
||||
|
@ -117,7 +115,6 @@ type BoardsTable struct {
|
|||
}
|
||||
|
||||
type BoardSectionsTable struct {
|
||||
IName string
|
||||
ID int
|
||||
Order int
|
||||
Hidden bool
|
||||
|
@ -150,7 +147,6 @@ type FiletypesTable struct {
|
|||
|
||||
// FrontTable represents the information (News, rules, etc) on the front page
|
||||
type FrontTable struct {
|
||||
IName string
|
||||
ID int
|
||||
Page int
|
||||
Order int
|
||||
|
@ -192,7 +188,6 @@ type PollResultsTable struct {
|
|||
|
||||
// PostTable represents each post in the database
|
||||
type PostTable struct {
|
||||
IName string
|
||||
ID int
|
||||
CurrentPage int
|
||||
NumPages int
|
||||
|
@ -330,7 +325,6 @@ type ThreadJSON struct {
|
|||
|
||||
// GochanConfig stores crucial info and is read from/written to gochan.json
|
||||
type GochanConfig struct {
|
||||
IName string //used by our template parser
|
||||
ListenIP string
|
||||
Port int
|
||||
FirstPage []string
|
||||
|
@ -351,67 +345,63 @@ type GochanConfig struct {
|
|||
DBprefix string
|
||||
DBkeepalive bool
|
||||
|
||||
Lockdown bool
|
||||
LockdownMessage string
|
||||
Sillytags []string
|
||||
UseSillytags bool
|
||||
Modboard string
|
||||
Lockdown bool `description:"Disables posting." default:"unchecked"`
|
||||
LockdownMessage string `description:"Message displayed when someone tries to post while the site is on lockdown."`
|
||||
Sillytags []string `description:"List of randomly selected staff tags separated by line, e.g. <span style=\"color: red;\">## Mod</span>, to be randomly assigned to posts if UseSillytags is checked. Don't include the \"## \""`
|
||||
UseSillytags bool `description:"Use Sillytags" default:"unchecked"`
|
||||
Modboard string `description:"A super secret clubhouse board that only staff can view/post to." default:"staff"`
|
||||
|
||||
SiteName string
|
||||
SiteSlogan string
|
||||
SiteHeaderURL string
|
||||
SiteWebfolder string
|
||||
SiteDomain string
|
||||
DomainRegex string
|
||||
SiteName string `description:"The name of the site that appears in the header of the front page." default:"Gochan"`
|
||||
SiteSlogan string `description:"The text that appears below SiteName on the home page"`
|
||||
SiteHeaderURL string `description:"To be honest, I'm not even sure what this does. It'll probably be removed later."`
|
||||
SiteWebfolder string `description:"The HTTP root appearing in the browser (e.g. https://gochan.org/<SiteWebFolder>" default:"/"`
|
||||
SiteDomain string `description:"The server's domain (duh). Do not edit this unless you know what you are doing or BAD THINGS WILL HAPPEN!" default:"127.0.0.1" critical:"true"`
|
||||
DomainRegex string `description:"Regular expression used for incoming request validation. Do not edit this unless you know what you are doing or BAD THINGS WILL HAPPEN!" default:"(https|http):\\\\/\\\\/(gochan\\\\.lunachan\\.net|gochan\\\\.org)\\/(.*)" critical:"true"`
|
||||
|
||||
Styles_img []string
|
||||
DefaultStyle_img string
|
||||
Styles_txt []string
|
||||
DefaultStyle_txt string
|
||||
Styles []string `description:"List of styles (one per line) that should be accessed online at /<SiteWebFolder>/css/<Style>/"`
|
||||
DefaultStyle string `description:"Style used by default (duh). This should appear in the list above or bad things might happen."`
|
||||
|
||||
AllowDuplicateImages bool
|
||||
AllowVideoUploads bool
|
||||
NewThreadDelay int
|
||||
ReplyDelay int
|
||||
MaxLineLength int
|
||||
ReservedTrips []interface{}
|
||||
AllowDuplicateImages bool `description:"Disabling this will cause gochan to reject a post if the image has already been uploaded for another post.<br />This may end up being removed or being made board-specific in the future." default:"checked"`
|
||||
AllowVideoUploads bool `description:"Allows users to upload .webm videos. <br />This may end up being removed or being made board-specific in the future."`
|
||||
NewThreadDelay int `description:"The amount of time in seconds that is required before an IP can make a new thread.<br />This may end up being removed or being made board-specific in the future." default:"30"`
|
||||
ReplyDelay int `description:"Same as the above, but for replies." default:"7"`
|
||||
MaxLineLength int `description:"Any line in a post that exceeds this will be split into two (or more) lines.<br />I'm not really sure why this is here, so it may end up being removed." default:"150"`
|
||||
ReservedTrips []string `description:"Secure tripcodes (!!Something) can be reserved here.<br />Each reservation should go on its own line and should look like this:<br />TripPassword1##Tripcode1<br />TripPassword2##Tripcode2"`
|
||||
|
||||
ThumbWidth int
|
||||
ThumbHeight int
|
||||
ThumbWidth_reply int
|
||||
ThumbHeight_reply int
|
||||
ThumbWidth_catalog int
|
||||
ThumbHeight_catalog int
|
||||
ThumbWidth int `description:"OP thumbnails use this as their max width.<br />To keep the aspect ratio, the image will be scaled down to the ThumbWidth or ThumbHeight, whichever is larger." default:"200"`
|
||||
ThumbHeight int `description:"OP thumbnails use this as their max height.<br />To keep the aspect ratio, the image will be scaled down to the ThumbWidth or ThumbHeight, whichever is larger." default:"200"`
|
||||
ThumbWidth_reply int `description:"Same as ThumbWidth and ThumbHeight but for reply images." default:"125"`
|
||||
ThumbHeight_reply int `description:"Same as ThumbWidth and ThumbHeight but for reply images." default:"125"`
|
||||
ThumbWidth_catalog int `description:"Same as ThumbWidth and ThumbHeight but for catalog images." default:"125"`
|
||||
ThumbHeight_catalog int `description:"Same as ThumbWidth and ThumbHeight but for catalog images." default:"125"`
|
||||
|
||||
ThreadsPerPage_img int
|
||||
ThreadsPerPage_txt int
|
||||
PostsPerThreadPage int
|
||||
RepliesOnBoardPage int
|
||||
StickyRepliesOnBoardPage int
|
||||
BanColors []interface{}
|
||||
BanMsg string
|
||||
EmbedWidth int
|
||||
EmbedHeight int
|
||||
ExpandButton bool
|
||||
ImagesOpenNewTab bool
|
||||
MakeURLsHyperlinked bool
|
||||
NewTabOnOutlinks bool
|
||||
EnableQuickReply bool
|
||||
ThreadsPerPage int `default:"15"`
|
||||
PostsPerThreadPage int `description:"Max number of replies to a thread to show on each thread page." default:"50"`
|
||||
RepliesOnBoardPage int `description:"Number of replies to a thread to show on the board page." default:"3"`
|
||||
StickyRepliesOnBoardPage int `description:"Same as above for stickied threads." default:"1"`
|
||||
BanColors []string `description:"Colors to be used for public ban messages (e.g. USER WAS BANNED FOR THIS POST).<br />Each entry should be on its own line, and should look something like this:<br />username1:#FF0000<br />username2:#FAF00F<br />username3:blue<br />Invalid entries/nonexistent usernames will show a warning and use the default red."`
|
||||
BanMsg string `description:"The default public ban message." default:"USER WAS BANNED FOR THIS POST"`
|
||||
EmbedWidth int `description:"The width for inline/expanded webm videos." default:"200"`
|
||||
EmbedHeight int `description:"The height for inline/expanded webm videos." default:"164"`
|
||||
ExpandButton bool `description:"If checked, adds [Embed] after a Youtube, Vimeo, etc link to toggle an inline video frame." default:"checked"`
|
||||
ImagesOpenNewTab bool `description:"If checked, thumbnails will open the respective image/video in a new tab instead of expanding them." default:"unchecked"`
|
||||
MakeURLsHyperlinked bool `description:"If checked, URLs in posts will be turned into a hyperlink. If unchecked, ExpandButton and NewTabOnOutlinks are ignored." default:"checked"`
|
||||
NewTabOnOutlinks bool `description:"If checked, links to external sites will open in a new tab." default:"checked"`
|
||||
EnableQuickReply bool `description:"If checked, an optional quick reply box is used. This may end up being removed." default:"checked"`
|
||||
|
||||
DateTimeFormat string
|
||||
DefaultBanReason string
|
||||
AkismetAPIKey string
|
||||
EnableGeoIP bool
|
||||
GeoIPDBlocation string // set to "cf" or the path to the db
|
||||
MaxRecentPosts int
|
||||
DateTimeFormat string `description:"The format used for dates. See <a href=\"https://golang.org/pkg/time/#Time.Format\">here</a> for more info."`
|
||||
AkismetAPIKey string `description:"The API key to be sent to Akismet for post spam checking. If the key is invalid, Akismet won't be used."`
|
||||
EnableGeoIP bool `description:"If checked, this enables the usage of GeoIP for posts." default:"checked"`
|
||||
GeoIPDBlocation string `description:"Specifies the location of the GeoIP database file. If you're using CloudFlare, you can set it to cf to rely on CloudFlare for GeoIP information." default:"/usr/share/GeoIP/GeoIP.dat"`
|
||||
MaxRecentPosts int `description:"The maximum number of posts to show on the Recent Posts list on the front page." default:"3"`
|
||||
// Verbosity = 0 for no debugging info. Critical errors and general output only
|
||||
// Verbosity = 1 for non-critical warnings and important info
|
||||
// Verbosity = 2 for all debugging/benchmarks/warnings
|
||||
Verbosity int
|
||||
EnableAppeals bool
|
||||
MaxModlogDays int
|
||||
RandomSeed string
|
||||
Version string
|
||||
Verbosity int `description:"The level of verbosity to use in error/warning messages. 0 = critical errors/startup messages, 1 = warnings, 2 = benchmarks/notices." default:"0"`
|
||||
EnableAppeals bool `description:"If checked, allow banned users to appeal their bans.<br />This will likely be removed (permanently allowing appeals) or made board-specific in the future." default:"checked"`
|
||||
MaxLogDays int `description:"The maximum number of days to keep messages in the moderation/staff log file."`
|
||||
RandomSeed string `critical:"true"`
|
||||
Version string `critical:"true"`
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
|
@ -426,7 +416,6 @@ func initConfig() {
|
|||
os.Exit(2)
|
||||
}
|
||||
|
||||
config.IName = "GochanConfig"
|
||||
if config.ListenIP == "" {
|
||||
println(0, "ListenIP not set in gochan.json, halting.")
|
||||
os.Exit(2)
|
||||
|
@ -559,22 +548,13 @@ func initConfig() {
|
|||
//config.DomainRegex = "(https|http):\\/\\/(" + config.SiteDomain + ")\\/(.*)"
|
||||
}
|
||||
|
||||
if config.Styles_img == nil {
|
||||
println(0, "Styles_img not set in gochan.json, halting.")
|
||||
if config.Styles == nil {
|
||||
println(0, "Styles not set in gochan.json, halting.")
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
if config.DefaultStyle_img == "" {
|
||||
config.DefaultStyle_img = config.Styles_img[0]
|
||||
}
|
||||
|
||||
if config.Styles_txt == nil {
|
||||
println(0, "Styles_txt not set in gochan.json, halting.")
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
if config.DefaultStyle_txt == "" {
|
||||
config.DefaultStyle_txt = config.Styles_txt[0]
|
||||
if config.DefaultStyle == "" {
|
||||
config.DefaultStyle = config.Styles[0]
|
||||
}
|
||||
|
||||
if config.NewThreadDelay == 0 {
|
||||
|
@ -615,12 +595,8 @@ func initConfig() {
|
|||
config.ThumbHeight_catalog = 50
|
||||
}
|
||||
|
||||
if config.ThreadsPerPage_img == 0 {
|
||||
config.ThreadsPerPage_img = 10
|
||||
}
|
||||
|
||||
if config.ThreadsPerPage_txt == 0 {
|
||||
config.ThreadsPerPage_txt = 15
|
||||
if config.ThreadsPerPage == 0 {
|
||||
config.ThreadsPerPage = 10
|
||||
}
|
||||
|
||||
if config.PostsPerThreadPage == 0 {
|
||||
|
@ -659,8 +635,8 @@ func initConfig() {
|
|||
config.MaxRecentPosts = 10
|
||||
}
|
||||
|
||||
if config.MaxModlogDays == 0 {
|
||||
config.MaxModlogDays = 15
|
||||
if config.MaxLogDays == 0 {
|
||||
config.MaxLogDays = 15
|
||||
}
|
||||
|
||||
if config.RandomSeed == "" {
|
||||
|
|
42
src/util.go
42
src/util.go
|
@ -31,6 +31,17 @@ const (
|
|||
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 abcdefghijklmnopqrstuvwxyz~!@#$%%^&*()_+{}[]-=:\"\\/?.>,<;:'"
|
||||
)
|
||||
|
||||
func arrToString(arr []string) string {
|
||||
var out string
|
||||
for i, val := range arr {
|
||||
out += val
|
||||
if i < len(arr)-1 {
|
||||
out += ","
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func benchmarkTimer(name string, givenTime time.Time, starting bool) (returnTime time.Time) {
|
||||
if starting {
|
||||
// starting benchmark test
|
||||
|
@ -194,7 +205,6 @@ func getBoardArr(parameterList map[string]interface{}, extra string) (boards []B
|
|||
// then append it to the boards array we are going to return
|
||||
for rows.Next() {
|
||||
board := new(BoardsTable)
|
||||
board.IName = "board"
|
||||
if err = rows.Scan(
|
||||
&board.ID,
|
||||
&board.Order,
|
||||
|
@ -283,7 +293,6 @@ func getPostArr(parameterList map[string]interface{}, extra string) (posts []Pos
|
|||
// then append it to the posts array we are going to return
|
||||
for rows.Next() {
|
||||
var post PostTable
|
||||
post.IName = "post"
|
||||
if err = rows.Scan(&post.ID, &post.BoardID, &post.ParentID, &post.Name, &post.Tripcode,
|
||||
&post.Email, &post.Subject, &post.MessageHTML, &post.MessageText, &post.Password, &post.Filename,
|
||||
&post.FilenameOriginal, &post.FileChecksum, &post.Filesize, &post.ImageW,
|
||||
|
@ -313,8 +322,6 @@ func getSectionArr(where string) (sections []interface{}, err error) {
|
|||
|
||||
for rows.Next() {
|
||||
section := new(BoardSectionsTable)
|
||||
section.IName = "section"
|
||||
|
||||
if err = rows.Scan(§ion.ID, §ion.Order, §ion.Hidden, §ion.Name, §ion.Abbreviation); err != nil {
|
||||
handleError(1, customError(err))
|
||||
return
|
||||
|
@ -352,15 +359,15 @@ func getFileExtension(filename string) (extension string) {
|
|||
return
|
||||
}
|
||||
|
||||
func getFormattedFilesize(size int) string {
|
||||
func getFormattedFilesize(size float64) string {
|
||||
if size < 1000 {
|
||||
return fmt.Sprintf("%fB", size)
|
||||
return fmt.Sprintf("%dB", int(size))
|
||||
} else if size <= 100000 {
|
||||
return fmt.Sprintf("%fKB", size/1024)
|
||||
} else if size <= 100000000 {
|
||||
return fmt.Sprintf("%fMB", size/1024/1024)
|
||||
return fmt.Sprintf("%fMB", size/1024.0/1024.0)
|
||||
}
|
||||
return fmt.Sprintf("%0.2fGB", size/1024/1024/1024)
|
||||
return fmt.Sprintf("%0.2fGB", size/1024.0/1024.0/1024.0)
|
||||
}
|
||||
|
||||
// returns the filename, line number, and function where getMetaInfo() is called
|
||||
|
@ -475,25 +482,30 @@ func bToA(b bool) string {
|
|||
}
|
||||
|
||||
// Checks the validity of the Akismet API key given in the config file.
|
||||
func checkAkismetAPIKey() {
|
||||
resp, err := http.PostForm("https://rest.akismet.com/1.1/verify-key", url.Values{"key": {config.AkismetAPIKey}, "blog": {"http://" + config.SiteDomain}})
|
||||
if err != nil {
|
||||
handleError(1, err.Error())
|
||||
func checkAkismetAPIKey(key string) error {
|
||||
if key == "" {
|
||||
return fmt.Errorf("Blank key given, Akismet won't be used.")
|
||||
}
|
||||
resp, err := http.PostForm("https://rest.akismet.com/1.1/verify-key", url.Values{"key": {key}, "blog": {"http://" + config.SiteDomain}})
|
||||
defer func() {
|
||||
if resp != nil && resp.Body != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
handleError(1, err.Error())
|
||||
return err
|
||||
}
|
||||
if string(body) == "invalid" {
|
||||
// This should disable the Akismet checks if the API key is not valid.
|
||||
errorLog.Print("Akismet API key is invalid, Akismet spam protection will be disabled.")
|
||||
config.AkismetAPIKey = ""
|
||||
errmsg := "Akismet API key is invalid, Akismet spam protection will be disabled."
|
||||
return fmt.Errorf(errmsg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks a given post for spam with Akismet. Only checks if Akismet API key is set.
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
<head>
|
||||
<title>Banned</title>
|
||||
<link rel="stylesheet" href="/css/global/front.css" />
|
||||
{{range $i, $style := .config.Styles_img}}
|
||||
<link rel="{{if not (isStyleDefault_img $style)}}alternate {{end}}stylesheet" href="/css/{{$style}}/front.css" />{{end}}
|
||||
{{range $i, $style := .config.Styles}}
|
||||
<link rel="{{if not (isStyleDefault $style)}}alternate {{end}}stylesheet" href="/css/{{$style}}/front.css" />{{end}}
|
||||
<script type="text/javascript">
|
||||
var styles = [{{range $i, $style := .config.Styles_img}}{{if gt $i 0}}, {{end}}"{{$style}}"{{end}}];
|
||||
var styles = [{{range $i, $style := .config.Styles}}{{if gt $i 0}}, {{end}}"{{$style}}"{{end}}];
|
||||
var webroot = "{{.config.SiteWebfolder}}"
|
||||
</script>
|
||||
<script type="text/javascript" src="/javascript/jquery-3.3.1.min.js"></script>
|
||||
|
|
|
@ -7,15 +7,15 @@
|
|||
<script type="text/javascript" src="/javascript/jquery-3.3.1.min.js"></script>
|
||||
<script type="text/javascript" src="/javascript/msgpack.js"></script>
|
||||
<script type="text/javascript">
|
||||
var styles = [{{range $ii, $style := .config.Styles_img}}{{if gt $ii 0}}, {{end}}"{{$style}}"{{end}}];
|
||||
var styles = [{{range $ii, $style := .config.Styles}}{{if gt $ii 0}}, {{end}}"{{$style}}"{{end}}];
|
||||
var webroot = "{{.config.SiteWebfolder}}"
|
||||
</script>
|
||||
<script type="text/javascript" src="/javascript/gochan.js"></script>
|
||||
<script type="text/javascript" src="/javascript/manage.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="/css/global/front.css" />
|
||||
{{range $i, $style := .config.Styles_img}}
|
||||
<link rel="{{if not (isStyleDefault_img $style)}}alternate {{end}}stylesheet" href="/css/{{$style}}/front.css" />{{end}}
|
||||
{{range $i, $style := .config.Styles}}
|
||||
<link rel="{{if not (isStyleDefault $style)}}alternate {{end}}stylesheet" href="/css/{{$style}}/front.css" />{{end}}
|
||||
<link rel="shortcut icon" href="/favicon.png">
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
{{else}}<title>/{{.board.Dir}}/ - {{.board.Title}}</title>{{end}}
|
||||
<script type="text/javascript" src="/javascript/jquery-3.3.1.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
var styles = [{{range $ii, $style := $.config.Styles_img}}{{if gt $ii 0}}, {{end}}"{{$style}}"{{end}}];
|
||||
var styles = [{{range $ii, $style := $.config.Styles}}{{if gt $ii 0}}, {{end}}"{{$style}}"{{end}}];
|
||||
var webroot = "{{$.config.SiteWebfolder}}";
|
||||
var thread_type = "thread";
|
||||
|
||||
|
@ -23,8 +23,8 @@
|
|||
<script type="text/javascript" src="/javascript/gochan.js"></script>
|
||||
<script type="text/javascript" src="/javascript/manage.js"></script>
|
||||
<link rel="stylesheet" href="/css/global/img.css" />
|
||||
{{range $_, $style := .config.Styles_img}}
|
||||
<link rel="{{if not (isStyleDefault_img $style)}}alternate {{end}}stylesheet" href="/css/{{$style}}/img.css" />{{end}}
|
||||
{{range $_, $style := .config.Styles}}
|
||||
<link rel="{{if not (isStyleDefault $style)}}alternate {{end}}stylesheet" href="/css/{{$style}}/img.css" />{{end}}
|
||||
<link rel="shortcut icon" href="/favicon.png" />
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<tr><td>Max image size</td><td><input type="text" name="maximagesize" value="4718592" /></td></tr>
|
||||
<tr><td>Max pages</td><td><input type="text" name="maxpages" value="11" /></td></tr>
|
||||
<tr><td>Default style</td><td><select name="defaultstyle" selected="">
|
||||
{{range $_, $style := .config.Styles_img}}<option value="{{$style}}">{{$style}}</option>{{end}}
|
||||
{{range $_, $style := .config.Styles}}<option value="{{$style}}">{{$style}}</option>{{end}}
|
||||
</select></td></tr>
|
||||
<tr><td>Locked</td><td><input type="checkbox" name="locked" {{if $.board.Locked}}checked{{end}}/></td></tr>
|
||||
<tr><td>Forced anonymity</td><td><input type="checkbox" name="forcedanon" {{if .board.ForcedAnon}}checked{{end}}/></td></tr>
|
||||
|
|
|
@ -1,22 +1,10 @@
|
|||
<h2>Config editor</h2>
|
||||
{{if ne .status ""}}{{.status}}<hr />{{end}}
|
||||
Some fields omitted because they can not be (safely) edited from the web interface while Gochan is running.
|
||||
Edit these directly in gochan.json, then restart Gochan.<br />
|
||||
<span class="warning">This config editor isn't fully stable so MAKE BACKUPS!</span>
|
||||
<form action="/manage?action=config" method="POST">
|
||||
<table>
|
||||
<tr><th>Name</th><th>Value</th><th>Description</th></tr>
|
||||
<tr><th>DocumentRoot</th><td><input type="text" value="{{.config.DocumentRoot}}"/></td></tr>
|
||||
<tr><th>TemplateDir</th><td><input type="text" value="{{.config.TemplateDir}}" /></td></tr>
|
||||
<tr><th>LogDir</th><td><input type="text" value="{{.config.LogDir}}" /></td></tr>
|
||||
|
||||
<tr><th>Lockdown</th><td><input type="checkbox" {{if .config.Lockdown}}checked{{end}}/></td></tr>
|
||||
<tr><th>LockdownMessage</th><td><input type="text" value="{{.config.LockdownMessage}}" /></td></tr>
|
||||
|
||||
<tr><th>UseSillytags</th><td><input type="checkbox" /></td></tr>
|
||||
<tr><th>Modboard</th><td><input type="text" value="{{.config.Modboard}}"></td></tr>
|
||||
|
||||
<tr><th>SiteName</th><td><input type="text" value="{{.config.SiteName}}" /></td></tr>
|
||||
<tr><th>SiteSlogan</th><td><input type="text" value="{{.config.SiteSlogan}}" /></td></tr>
|
||||
<tr><th>SiteHeaderURL</th><td><input type="text" value="{{.config.SiteHeaderURL}}" /></td></tr>
|
||||
{{/*<tr><th>SiteWebfolder</th><td><input type="text" value="{{.config.SiteWebFolder}}" /></td></tr>*/}}
|
||||
<tr><th>DomainRegex</th><td><input type="text" value="{{.config.DomainRegex}}" /></td><td>Don't touch this unless you know what you're doing! This could fuck your shit up<br />Default: (https|http)://(.*)/(.*)</td></tr>
|
||||
</table>
|
||||
<input name="do" value="save" type="hidden" />
|
||||
{{generateConfigTable}}<br />
|
||||
<input type="submit" />
|
||||
</form>
|
|
@ -1,10 +1,10 @@
|
|||
<title>Gochan Manage page</title>
|
||||
<link rel="stylesheet" href="/css/global/manage.css" />
|
||||
{{range $i, $style := .Styles_img}}
|
||||
<link rel="{{if not (isStyleDefault_img $style)}}alternate {{end}}stylesheet" href="/css/{{$style}}/manage.css" />{{end}}
|
||||
{{range $i, $style := .Styles}}
|
||||
<link rel="{{if not (isStyleDefault $style)}}alternate {{end}}stylesheet" href="/css/{{$style}}/manage.css" />{{end}}
|
||||
<link rel="shortcut icon" href="/favicon.png" />
|
||||
<script type="text/javascript">
|
||||
var styles = [{{range $i, $style := .Styles_img}}{{if gt $i 0}}, {{end}}"{{$style}}"{{end}}];
|
||||
var styles = [{{range $i, $style := .Styles}}{{if gt $i 0}}, {{end}}"{{$style}}"{{end}}];
|
||||
var webroot = "{{.SiteWebfolder}}"
|
||||
</script>
|
||||
<script type="text/javascript" src="/javascript/jquery-3.3.1.min.js"></script>
|
||||
|
|
|
@ -6,14 +6,14 @@
|
|||
<title>Edit Post</title>
|
||||
<script type="text/javascript" src="/javascript/jquery-3.3.1.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
var styles = [{{range $ii, $style := $.config.Styles_img}}{{if gt $ii 0}}, {{end}}"{{$style}}"{{end}}];
|
||||
var styles = [{{range $ii, $style := $.config.Styles}}{{if gt $ii 0}}, {{end}}"{{$style}}"{{end}}];
|
||||
var webroot = "{{$.config.SiteWebfolder}}";
|
||||
</script>
|
||||
<script type="text/javascript" src="/javascript/gochan.js"></script>
|
||||
<script type="text/javascript" src="/javascript/manage.js"></script>
|
||||
<link rel="stylesheet" href="/css/global/img.css" />
|
||||
{{range $_, $style := .config.Styles_img}}
|
||||
<link rel="{{if not (isStyleDefault_img $style)}}alternate {{end}}stylesheet" href="/css/{{$style}}/img.css" />{{end}}
|
||||
{{range $_, $style := .config.Styles}}
|
||||
<link rel="{{if not (isStyleDefault $style)}}alternate {{end}}stylesheet" href="/css/{{$style}}/img.css" />{{end}}
|
||||
<link rel="shortcut icon" href="/favicon.png" />
|
||||
</head>
|
||||
<body>
|
||||
|
|
2
version
2
version
|
@ -1 +1 @@
|
|||
1.10.3
|
||||
1.11.0
|
Loading…
Add table
Add a link
Reference in a new issue