1
0
Fork 0
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:
Joshua Merrell 2018-08-09 00:04:45 -07:00
parent 0ef760dd61
commit c700e4c0ce
20 changed files with 415 additions and 379 deletions

View file

@ -34,25 +34,17 @@
"SiteWebfolder": "/", "SiteWebfolder": "/",
"DomainRegex": "(https|http):\\/\\/(gochan\\.lunachan\\.net|gochan\\.org)\\/(.*)", "DomainRegex": "(https|http):\\/\\/(gochan\\.lunachan\\.net|gochan\\.org)\\/(.*)",
"Styles_img": ["pipes","efchan"], "Styles": ["pipes","efchan"],
"DefaultStyle_img": "pipes", "DefaultStyle": "pipes",
"Styles_txt": ["efchan,pipes"],
"DefaultStyle_txt": "pipes",
"AllowDuplicateImages": true, "AllowDuplicateImages": true,
"AllowVideoUploads": true, "AllowVideoUploads": true,
"NewThreadDelay": 30, "NewThreadDelay": 30,
"ReplyDelay": 7, "ReplyDelay": 7,
"MaxLineLength": 150, "MaxLineLength": 150,
"ReversedTrips": [ "ReservedTrips": [
{ "thischangesto##this",
"from": "##thischangesto", "andthischangesto##this"
"to": "this"
},
{
"from": "##andthischangesto",
"to": "this"
}
], ],
"ThumbWidth": 200, "ThumbWidth": 200,
@ -62,18 +54,15 @@
"ThumbWidth_catalog": 50, "ThumbWidth_catalog": 50,
"ThumbHeight_catalog": 50, "ThumbHeight_catalog": 50,
"ThreadsPerPage_img": 4, "ThreadsPerPage": 15,
"ThreadsPerPage_txt": 15, "PostsPerThreadPage": 50,
"PostsPerThreadPage": 4,
"RepliesOnBoardPage": 3, "RepliesOnBoardPage": 3,
"StickyRepliesOnBoardPage": 1, "StickyRepliesOnBoardPage": 1,
"BanColors": [ "BanColors": [
{ "admin:#0000A0",
"Role": "admin", "somemod:blue"
"Color": "#0000A0"
}
], ],
"BanMsg": "<br /><span style=\"color:##BANCOLOR## \"><b>(USER WAS BANNED FOR THIS POST)</b></span>", "BanMsg": "USER WAS BANNED FOR THIS POST",
"EmbedWidth": 200, "EmbedWidth": 200,
"EmbedHeight": 164, "EmbedHeight": 164,
"ExpandButton": true, "ExpandButton": true,
@ -81,9 +70,7 @@
"MakeURLsHyperlinked": true, "MakeURLsHyperlinked": true,
"NewTabOnOutlinks": true, "NewTabOnOutlinks": true,
"DateTimeFormat": "Mon, January 02, 2006 15:04 PM", "DateTimeFormat": "Mon, January 02, 2006 15:04 PM",
"DefaultBanReason": "",
"AkismetAPIKey": "", "AkismetAPIKey": "",
"EnableGeoIP": true, "EnableGeoIP": true,
"_comment": "set GeoIPDBlocation to cf to use Cloudflare's GeoIP", "_comment": "set GeoIPDBlocation to cf to use Cloudflare's GeoIP",
@ -91,7 +78,7 @@
"MaxRecentPosts": 3, "MaxRecentPosts": 3,
"Verbosity": 0, "Verbosity": 0,
"EnableAppeals": true, "EnableAppeals": true,
"MaxModlogDays": 14, "MaxLogDays": 14,
"_comment": "Set RandomSeed to a (preferrably large) string of letters and numbers", "_comment": "Set RandomSeed to a (preferrably large) string of letters and numbers",
"RandomSeed": "" "RandomSeed": ""
} }

View file

@ -58,4 +58,18 @@ table, table th, td, tr {
h1, h2 { h1, h2 {
padding-left: 4px; padding-left: 4px;
padding-right: 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;
} }

View file

@ -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;
}

View file

@ -4,7 +4,6 @@ import (
"os" "os"
) )
// set in Makefile via -ldflags
var version string var version string
var buildtimeString string // set in Makefile, format: YRMMDD.HHMM var buildtimeString string // set in Makefile, format: YRMMDD.HHMM

View file

@ -3,12 +3,15 @@ package main
import ( import (
"bytes" "bytes"
"database/sql" "database/sql"
"encoding/json"
"fmt" "fmt"
"io/ioutil"
"net/http" "net/http"
"os" "os"
"path" "path"
"regexp" "regexp"
"strconv" "strconv"
"strings"
"time" "time"
"golang.org/x/crypto/bcrypt" "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 //returns 0 for successful, 1 for password mismatch, and 2 for other
domain := request.Host domain := request.Host
var err error var err error
chopPortNumRegex := regexp.MustCompile("(.+|\\w+):(\\d+)$") chopPortNumRegex := regexp.MustCompile(`(.+|\w+):(\d+)$`)
domain = chopPortNumRegex.Split(domain, -1)[0] domain = chopPortNumRegex.Split(domain, -1)[0]
if !validReferrer(request) { if !validReferrer(request) {
@ -208,22 +211,205 @@ var manage_functions = map[string]ManageFunction{
Permissions: 3, Permissions: 3,
Callback: func(writer http.ResponseWriter, request *http.Request) (html string) { Callback: func(writer http.ResponseWriter, request *http.Request) (html string) {
do := request.FormValue("do") do := request.FormValue("do")
var status string
if do == "save" { if do == "save" {
// configJSON, err := json.Marshal(config) configJSON, err := json.MarshalIndent(config, "", "\t")
// if err != nil { if err != nil {
// html += err.Error() status += err.Error() + "<br />\n"
// return } 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) ReplyDelay, err := strconv.Atoi(request.PostFormValue("ReplyDelay"))
// if err != nil { if err != nil {
// html += "Error writing \"gochan.json\": %s\n" + err.Error() status += err.Error() + "<br />\n"
// return } 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("") manageConfigBuffer := bytes.NewBufferString("")
if err := manage_config_tmpl.Execute(manageConfigBuffer,
if err := manage_config_tmpl.Execute(manageConfigBuffer, nil); err != nil { map[string]interface{}{"config": config, "status": status},
); err != nil {
html += handleError(1, err.Error()) html += handleError(1, err.Error())
return return
} }
@ -576,7 +762,8 @@ var manage_functions = map[string]ManageFunction{
if err != nil { if err != nil {
board.MaxPages = 11 board.MaxPages = 11
} }
board.DefaultStyle = request.FormValue("defaultstyle")
board.DefaultStyle = strings.Trim(request.FormValue("defaultstyle"), "\n")
board.Locked = (request.FormValue("locked") == "on") board.Locked = (request.FormValue("locked") == "on")
board.ForcedAnon = (request.FormValue("forcedanon") == "on") board.ForcedAnon = (request.FormValue("forcedanon") == "on")

View file

@ -112,7 +112,6 @@ func buildBoardPages(board *BoardsTable) (html string) {
for _, op := range op_posts { for _, op := range op_posts {
var thread Thread var thread Thread
var posts_in_thread []PostTable var posts_in_thread []PostTable
thread.IName = "thread"
// Get the number of replies to this 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` = ?", 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 return
} else { } else {
// Create the archive pages. // Create the archive pages.
thread_pages = paginate(config.ThreadsPerPage_img, threads) thread_pages = paginate(config.ThreadsPerPage, threads)
board.NumPages = len(thread_pages) - 1 board.NumPages = len(thread_pages) - 1
// Create array of page wrapper objects, and open the file. // Create array of page wrapper objects, and open the file.
@ -475,7 +474,6 @@ func buildFrontPage() (html string) {
for rows.Next() { for rows.Next() {
frontpage := new(FrontTable) frontpage := new(FrontTable)
frontpage.IName = "front page"
if err = rows.Scan(&frontpage.ID, &frontpage.Page, &frontpage.Order, &frontpage.Subject, if err = rows.Scan(&frontpage.ID, &frontpage.Page, &frontpage.Order, &frontpage.Subject,
&frontpage.Message, &frontpage.Timestamp, &frontpage.Poster, &frontpage.Email); err != nil { &frontpage.Message, &frontpage.Timestamp, &frontpage.Poster, &frontpage.Email); err != nil {
return handleError(1, err.Error()) return handleError(1, err.Error())
@ -537,7 +535,7 @@ func buildBoardListJSON() (html string) {
for _, board_int := range allBoards { for _, board_int := range allBoards {
board := board_int.(BoardsTable) board := board_int.(BoardsTable)
board_obj := BoardJSON{BoardName: board.Dir, Title: board.Title, WorkSafeBoard: 1, 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, MaxMessageLength: board.MaxMessageLength, BumpLimit: 200, ImageLimit: board.NoImagesAfter,
Cooldowns: cooldowns_obj, Description: board.Description, IsArchived: 0} Cooldowns: cooldowns_obj, Description: board.Description, IsArchived: 0}
if board.EnableNSFW { 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 // Checks check poster's name/tripcode/file checksum (from PostTable post) for banned status
// returns true if the user is banned // returns true if the user is banned
func checkBannedStatus(post *PostTable, writer http.ResponseWriter) ([]interface{}, error) { func checkBannedStatus(post *PostTable, writer http.ResponseWriter) ([]interface{}, error) {
var isExpired bool var banEntry BanlistTable
var ban_entry BanlistTable
var interfaces []interface{} var interfaces []interface{}
// var count int // var count int
// var search string // var search string
err := queryRowSQL("SELECT `ip`, `name`, `tripcode`, `message`, `boards`, `timestamp`, `expires`, `appeal_at` FROM `"+config.DBprefix+"banlist` WHERE `ip` = ?", err := queryRowSQL("SELECT `ip`, `name`, `tripcode`, `message`, `boards`, `timestamp`, `expires`, `appeal_at` FROM `"+config.DBprefix+"banlist` WHERE `ip` = ?",
[]interface{}{&post.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 { if err == sql.ErrNoRows {
// the user isn't banned // 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()) handleError(1, "Error checking banned status: "+err.Error())
return interfaces, err 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 // if it is expired, send a message saying that it's expired, but still post
println(1, "expired") println(1, "expired")
return interfaces, nil return interfaces, nil
} }
// the user's IP is in the banlist. Check if the ban has expired // 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 // 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. // 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 return interfaces, nil
} }
@ -633,8 +630,8 @@ func createImageThumbnail(image_obj image.Image, size string) image.Image {
return image_obj return image_obj
} }
thumb_w, thumb_h := getThumbnailSize(old_rect.Max.X, old_rect.Max.Y, size) thumbW, thumbH := 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 image_obj = imaging.Resize(image_obj, thumbW, thumbH, imaging.CatmullRom) // resize to 600x400 px using CatmullRom cubic filter
return image_obj 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 // 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 thumbWidth int
var thumbHeight 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 thumbHeight = config.ThumbHeight_catalog
} }
if w == h { if w == h {
new_w = thumbWidth newWidth = thumbWidth
new_h = thumbHeight newHeight = thumbHeight
} else { } else {
var percent float32 var percent float32
if w > h { if w > h {
@ -705,8 +702,8 @@ func getThumbnailSize(w int, h int, size string) (new_w int, new_h int) {
} else { } else {
percent = float32(thumbHeight) / float32(h) percent = float32(thumbHeight) / float32(h)
} }
new_w = int(float32(w) * percent) newWidth = int(float32(w) * percent)
new_h = int(float32(h) * percent) newHeight = int(float32(h) * percent)
} }
return 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 // inserts prepared post object into the SQL table so that it can be rendered
func insertPost(post PostTable, bump bool) (sql.Result, error) { func insertPost(post PostTable, bump bool) (sql.Result, error) {
result, err := execSQL( 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`)"+ "(`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(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
post.BoardID, post.ParentID, post.Name, post.Tripcode, post.Email, 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+)$") chopPortNumRegex := regexp.MustCompile("(.+|\\w+):(\\d+)$")
domain = chopPortNumRegex.Split(domain, -1)[0] domain = chopPortNumRegex.Split(domain, -1)[0]
post.IName = "post"
post.ParentID, _ = strconv.Atoi(request.FormValue("threadid")) post.ParentID, _ = strconv.Atoi(request.FormValue("threadid"))
post.BoardID, _ = strconv.Atoi(request.FormValue("boardid")) post.BoardID, _ = strconv.Atoi(request.FormValue("boardid"))

View file

@ -131,23 +131,23 @@ func initServer() {
server.namespaces = make(map[string]func(http.ResponseWriter, *http.Request)) server.namespaces = make(map[string]func(http.ResponseWriter, *http.Request))
// Check if Akismet API key is usable at startup. // Check if Akismet API key is usable at startup.
if config.AkismetAPIKey != "" { if err = checkAkismetAPIKey(config.AkismetAPIKey); err != nil {
checkAkismetAPIKey() config.AkismetAPIKey = ""
handleError(0, "%s", err.Error())
} }
// Compile regex for checking referrers. // Compile regex for checking referrers.
referrerRegex = regexp.MustCompile(config.DomainRegex) 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("manage", callManageFunction)
server.AddNamespace("post", makePost) server.AddNamespace("post", makePost)
server.AddNamespace("util", utilHandler) 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 // eventually plugins will be able to register new namespaces. Or they will be restricted to something like /plugin
if config.UseFastCGI { if config.UseFastCGI {

View file

@ -69,7 +69,7 @@ func connectToSQLServer() {
} }
if newInstall { 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( if _, err = db.Exec(
"INSERT INTO `" + config.DBname + "`.`" + config.DBprefix + "staff` " + "INSERT INTO `" + config.DBname + "`.`" + config.DBprefix + "staff` " +
"(`username`, `password_checksum`, `salt`, `rank`) " + "(`username`, `password_checksum`, `salt`, `rank`) " +

View file

@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"html" "html"
"reflect"
"strconv" "strconv"
"strings" "strings"
"text/template" "text/template"
@ -106,8 +107,9 @@ var funcMap = template.FuncMap{
return a == b return a == b
}, },
"intToString": strconv.Itoa, "intToString": strconv.Itoa,
"isStyleDefault_img": func(style string) bool { "arrToString": arrToString,
return style == config.DefaultStyle_img "isStyleDefault": func(style string) bool {
return style == config.DefaultStyle
}, },
"formatTimestamp": humanReadableTime, "formatTimestamp": humanReadableTime,
"getThreadID": func(post_i interface{}) (thread int) { "getThreadID": func(post_i interface{}) (thread int) {
@ -152,12 +154,10 @@ var funcMap = template.FuncMap{
"formatFilesize": func(size_int int) string { "formatFilesize": func(size_int int) string {
size := float32(size_int) size := float32(size_int)
if size < 1000 { if size < 1000 {
return fmt.Sprintf("%fB", size) return fmt.Sprintf("%d B", size_int)
} else if size <= 100000 { } else if size <= 100000 {
//size = size * 0.2
return fmt.Sprintf("%0.1f KB", size/1024) return fmt.Sprintf("%0.1f KB", size/1024)
} else if size <= 100000000 { } else if size <= 100000000 {
//size = size * 0.2
return fmt.Sprintf("%0.2f MB", size/1024/1024) return fmt.Sprintf("%0.2f MB", size/1024/1024)
} }
return fmt.Sprintf("%0.2f GB", size/1024/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 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 ( var (

View file

@ -36,7 +36,6 @@ type RecentPost struct {
} }
type Thread struct { type Thread struct {
IName string
OP PostTable OP PostTable
NumReplies int NumReplies int
NumImages int NumImages int
@ -81,7 +80,6 @@ type BannedHashesTable struct {
} }
type BoardsTable struct { type BoardsTable struct {
IName string
ID int ID int
CurrentPage int CurrentPage int
NumPages int NumPages int
@ -117,7 +115,6 @@ type BoardsTable struct {
} }
type BoardSectionsTable struct { type BoardSectionsTable struct {
IName string
ID int ID int
Order int Order int
Hidden bool Hidden bool
@ -150,7 +147,6 @@ type FiletypesTable struct {
// FrontTable represents the information (News, rules, etc) on the front page // FrontTable represents the information (News, rules, etc) on the front page
type FrontTable struct { type FrontTable struct {
IName string
ID int ID int
Page int Page int
Order int Order int
@ -192,7 +188,6 @@ type PollResultsTable struct {
// PostTable represents each post in the database // PostTable represents each post in the database
type PostTable struct { type PostTable struct {
IName string
ID int ID int
CurrentPage int CurrentPage int
NumPages int NumPages int
@ -330,7 +325,6 @@ type ThreadJSON struct {
// GochanConfig stores crucial info and is read from/written to gochan.json // GochanConfig stores crucial info and is read from/written to gochan.json
type GochanConfig struct { type GochanConfig struct {
IName string //used by our template parser
ListenIP string ListenIP string
Port int Port int
FirstPage []string FirstPage []string
@ -351,67 +345,63 @@ type GochanConfig struct {
DBprefix string DBprefix string
DBkeepalive bool DBkeepalive bool
Lockdown bool Lockdown bool `description:"Disables posting." default:"unchecked"`
LockdownMessage string LockdownMessage string `description:"Message displayed when someone tries to post while the site is on lockdown."`
Sillytags []string 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 UseSillytags bool `description:"Use Sillytags" default:"unchecked"`
Modboard string Modboard string `description:"A super secret clubhouse board that only staff can view/post to." default:"staff"`
SiteName string SiteName string `description:"The name of the site that appears in the header of the front page." default:"Gochan"`
SiteSlogan string SiteSlogan string `description:"The text that appears below SiteName on the home page"`
SiteHeaderURL string SiteHeaderURL string `description:"To be honest, I'm not even sure what this does. It'll probably be removed later."`
SiteWebfolder string SiteWebfolder string `description:"The HTTP root appearing in the browser (e.g. https://gochan.org/&lt;SiteWebFolder&gt;" default:"/"`
SiteDomain string 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 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 Styles []string `description:"List of styles (one per line) that should be accessed online at /&lt;SiteWebFolder&gt;/css/&lt;Style&gt;/"`
DefaultStyle_img string DefaultStyle string `description:"Style used by default (duh). This should appear in the list above or bad things might happen."`
Styles_txt []string
DefaultStyle_txt string
AllowDuplicateImages bool 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 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 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 ReplyDelay int `description:"Same as the above, but for replies." default:"7"`
MaxLineLength int 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 []interface{} 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 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 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 ThumbWidth_reply int `description:"Same as ThumbWidth and ThumbHeight but for reply images." default:"125"`
ThumbHeight_reply int ThumbHeight_reply int `description:"Same as ThumbWidth and ThumbHeight but for reply images." default:"125"`
ThumbWidth_catalog int ThumbWidth_catalog int `description:"Same as ThumbWidth and ThumbHeight but for catalog images." default:"125"`
ThumbHeight_catalog int ThumbHeight_catalog int `description:"Same as ThumbWidth and ThumbHeight but for catalog images." default:"125"`
ThreadsPerPage_img int ThreadsPerPage int `default:"15"`
ThreadsPerPage_txt int PostsPerThreadPage int `description:"Max number of replies to a thread to show on each thread page." default:"50"`
PostsPerThreadPage int RepliesOnBoardPage int `description:"Number of replies to a thread to show on the board page." default:"3"`
RepliesOnBoardPage int StickyRepliesOnBoardPage int `description:"Same as above for stickied threads." default:"1"`
StickyRepliesOnBoardPage int 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."`
BanColors []interface{} BanMsg string `description:"The default public ban message." default:"USER WAS BANNED FOR THIS POST"`
BanMsg string EmbedWidth int `description:"The width for inline/expanded webm videos." default:"200"`
EmbedWidth int EmbedHeight int `description:"The height for inline/expanded webm videos." default:"164"`
EmbedHeight int ExpandButton bool `description:"If checked, adds [Embed] after a Youtube, Vimeo, etc link to toggle an inline video frame." default:"checked"`
ExpandButton bool ImagesOpenNewTab bool `description:"If checked, thumbnails will open the respective image/video in a new tab instead of expanding them." default:"unchecked"`
ImagesOpenNewTab bool MakeURLsHyperlinked bool `description:"If checked, URLs in posts will be turned into a hyperlink. If unchecked, ExpandButton and NewTabOnOutlinks are ignored." default:"checked"`
MakeURLsHyperlinked bool NewTabOnOutlinks bool `description:"If checked, links to external sites will open in a new tab." default:"checked"`
NewTabOnOutlinks bool EnableQuickReply bool `description:"If checked, an optional quick reply box is used. This may end up being removed." default:"checked"`
EnableQuickReply bool
DateTimeFormat string DateTimeFormat string `description:"The format used for dates. See <a href=\"https://golang.org/pkg/time/#Time.Format\">here</a> for more info."`
DefaultBanReason string 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."`
AkismetAPIKey string EnableGeoIP bool `description:"If checked, this enables the usage of GeoIP for posts." default:"checked"`
EnableGeoIP bool 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"`
GeoIPDBlocation string // set to "cf" or the path to the db MaxRecentPosts int `description:"The maximum number of posts to show on the Recent Posts list on the front page." default:"3"`
MaxRecentPosts int
// Verbosity = 0 for no debugging info. Critical errors and general output only // Verbosity = 0 for no debugging info. Critical errors and general output only
// Verbosity = 1 for non-critical warnings and important info // Verbosity = 1 for non-critical warnings and important info
// Verbosity = 2 for all debugging/benchmarks/warnings // Verbosity = 2 for all debugging/benchmarks/warnings
Verbosity int 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 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"`
MaxModlogDays int MaxLogDays int `description:"The maximum number of days to keep messages in the moderation/staff log file."`
RandomSeed string RandomSeed string `critical:"true"`
Version string Version string `critical:"true"`
} }
func initConfig() { func initConfig() {
@ -426,7 +416,6 @@ func initConfig() {
os.Exit(2) os.Exit(2)
} }
config.IName = "GochanConfig"
if config.ListenIP == "" { if config.ListenIP == "" {
println(0, "ListenIP not set in gochan.json, halting.") println(0, "ListenIP not set in gochan.json, halting.")
os.Exit(2) os.Exit(2)
@ -559,22 +548,13 @@ func initConfig() {
//config.DomainRegex = "(https|http):\\/\\/(" + config.SiteDomain + ")\\/(.*)" //config.DomainRegex = "(https|http):\\/\\/(" + config.SiteDomain + ")\\/(.*)"
} }
if config.Styles_img == nil { if config.Styles == nil {
println(0, "Styles_img not set in gochan.json, halting.") println(0, "Styles not set in gochan.json, halting.")
os.Exit(2) os.Exit(2)
} }
if config.DefaultStyle_img == "" { if config.DefaultStyle == "" {
config.DefaultStyle_img = config.Styles_img[0] config.DefaultStyle = config.Styles[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.NewThreadDelay == 0 { if config.NewThreadDelay == 0 {
@ -615,12 +595,8 @@ func initConfig() {
config.ThumbHeight_catalog = 50 config.ThumbHeight_catalog = 50
} }
if config.ThreadsPerPage_img == 0 { if config.ThreadsPerPage == 0 {
config.ThreadsPerPage_img = 10 config.ThreadsPerPage = 10
}
if config.ThreadsPerPage_txt == 0 {
config.ThreadsPerPage_txt = 15
} }
if config.PostsPerThreadPage == 0 { if config.PostsPerThreadPage == 0 {
@ -659,8 +635,8 @@ func initConfig() {
config.MaxRecentPosts = 10 config.MaxRecentPosts = 10
} }
if config.MaxModlogDays == 0 { if config.MaxLogDays == 0 {
config.MaxModlogDays = 15 config.MaxLogDays = 15
} }
if config.RandomSeed == "" { if config.RandomSeed == "" {

View file

@ -31,6 +31,17 @@ const (
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 abcdefghijklmnopqrstuvwxyz~!@#$%%^&*()_+{}[]-=:\"\\/?.>,<;:'" 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) { func benchmarkTimer(name string, givenTime time.Time, starting bool) (returnTime time.Time) {
if starting { if starting {
// starting benchmark test // 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 // then append it to the boards array we are going to return
for rows.Next() { for rows.Next() {
board := new(BoardsTable) board := new(BoardsTable)
board.IName = "board"
if err = rows.Scan( if err = rows.Scan(
&board.ID, &board.ID,
&board.Order, &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 // then append it to the posts array we are going to return
for rows.Next() { for rows.Next() {
var post PostTable var post PostTable
post.IName = "post"
if err = rows.Scan(&post.ID, &post.BoardID, &post.ParentID, &post.Name, &post.Tripcode, 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.Email, &post.Subject, &post.MessageHTML, &post.MessageText, &post.Password, &post.Filename,
&post.FilenameOriginal, &post.FileChecksum, &post.Filesize, &post.ImageW, &post.FilenameOriginal, &post.FileChecksum, &post.Filesize, &post.ImageW,
@ -313,8 +322,6 @@ func getSectionArr(where string) (sections []interface{}, err error) {
for rows.Next() { for rows.Next() {
section := new(BoardSectionsTable) section := new(BoardSectionsTable)
section.IName = "section"
if err = rows.Scan(&section.ID, &section.Order, &section.Hidden, &section.Name, &section.Abbreviation); err != nil { if err = rows.Scan(&section.ID, &section.Order, &section.Hidden, &section.Name, &section.Abbreviation); err != nil {
handleError(1, customError(err)) handleError(1, customError(err))
return return
@ -352,15 +359,15 @@ func getFileExtension(filename string) (extension string) {
return return
} }
func getFormattedFilesize(size int) string { func getFormattedFilesize(size float64) string {
if size < 1000 { if size < 1000 {
return fmt.Sprintf("%fB", size) return fmt.Sprintf("%dB", int(size))
} else if size <= 100000 { } else if size <= 100000 {
return fmt.Sprintf("%fKB", size/1024) return fmt.Sprintf("%fKB", size/1024)
} else if size <= 100000000 { } 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 // 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. // Checks the validity of the Akismet API key given in the config file.
func checkAkismetAPIKey() { func checkAkismetAPIKey(key string) error {
resp, err := http.PostForm("https://rest.akismet.com/1.1/verify-key", url.Values{"key": {config.AkismetAPIKey}, "blog": {"http://" + config.SiteDomain}}) if key == "" {
if err != nil { return fmt.Errorf("Blank key given, Akismet won't be used.")
handleError(1, err.Error())
} }
resp, err := http.PostForm("https://rest.akismet.com/1.1/verify-key", url.Values{"key": {key}, "blog": {"http://" + config.SiteDomain}})
defer func() { defer func() {
if resp != nil && resp.Body != nil { if resp != nil && resp.Body != nil {
resp.Body.Close() resp.Body.Close()
} }
}() }()
if err != nil {
return err
}
body, err := ioutil.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
handleError(1, err.Error()) return err
} }
if string(body) == "invalid" { if string(body) == "invalid" {
// This should disable the Akismet checks if the API key is not valid. // 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.") errmsg := "Akismet API key is invalid, Akismet spam protection will be disabled."
config.AkismetAPIKey = "" return fmt.Errorf(errmsg)
} }
return nil
} }
// Checks a given post for spam with Akismet. Only checks if Akismet API key is set. // Checks a given post for spam with Akismet. Only checks if Akismet API key is set.

View file

@ -3,10 +3,10 @@
<head> <head>
<title>Banned</title> <title>Banned</title>
<link rel="stylesheet" href="/css/global/front.css" /> <link rel="stylesheet" href="/css/global/front.css" />
{{range $i, $style := .config.Styles_img}} {{range $i, $style := .config.Styles}}
<link rel="{{if not (isStyleDefault_img $style)}}alternate {{end}}stylesheet" href="/css/{{$style}}/front.css" />{{end}} <link rel="{{if not (isStyleDefault $style)}}alternate {{end}}stylesheet" href="/css/{{$style}}/front.css" />{{end}}
<script type="text/javascript"> <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}}" var webroot = "{{.config.SiteWebfolder}}"
</script> </script>
<script type="text/javascript" src="/javascript/jquery-3.3.1.min.js"></script> <script type="text/javascript" src="/javascript/jquery-3.3.1.min.js"></script>

View file

@ -7,15 +7,15 @@
<script type="text/javascript" src="/javascript/jquery-3.3.1.min.js"></script> <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" src="/javascript/msgpack.js"></script>
<script type="text/javascript"> <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 webroot = "{{.config.SiteWebfolder}}"
</script> </script>
<script type="text/javascript" src="/javascript/gochan.js"></script> <script type="text/javascript" src="/javascript/gochan.js"></script>
<script type="text/javascript" src="/javascript/manage.js"></script> <script type="text/javascript" src="/javascript/manage.js"></script>
<link rel="stylesheet" href="/css/global/front.css" /> <link rel="stylesheet" href="/css/global/front.css" />
{{range $i, $style := .config.Styles_img}} {{range $i, $style := .config.Styles}}
<link rel="{{if not (isStyleDefault_img $style)}}alternate {{end}}stylesheet" href="/css/{{$style}}/front.css" />{{end}} <link rel="{{if not (isStyleDefault $style)}}alternate {{end}}stylesheet" href="/css/{{$style}}/front.css" />{{end}}
<link rel="shortcut icon" href="/favicon.png"> <link rel="shortcut icon" href="/favicon.png">
</head> </head>
<body> <body>

View file

@ -12,7 +12,7 @@
{{else}}<title>/{{.board.Dir}}/ - {{.board.Title}}</title>{{end}} {{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" src="/javascript/jquery-3.3.1.min.js"></script>
<script type="text/javascript"> <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 webroot = "{{$.config.SiteWebfolder}}";
var thread_type = "thread"; var thread_type = "thread";
@ -23,8 +23,8 @@
<script type="text/javascript" src="/javascript/gochan.js"></script> <script type="text/javascript" src="/javascript/gochan.js"></script>
<script type="text/javascript" src="/javascript/manage.js"></script> <script type="text/javascript" src="/javascript/manage.js"></script>
<link rel="stylesheet" href="/css/global/img.css" /> <link rel="stylesheet" href="/css/global/img.css" />
{{range $_, $style := .config.Styles_img}} {{range $_, $style := .config.Styles}}
<link rel="{{if not (isStyleDefault_img $style)}}alternate {{end}}stylesheet" href="/css/{{$style}}/img.css" />{{end}} <link rel="{{if not (isStyleDefault $style)}}alternate {{end}}stylesheet" href="/css/{{$style}}/img.css" />{{end}}
<link rel="shortcut icon" href="/favicon.png" /> <link rel="shortcut icon" href="/favicon.png" />
</head> </head>
<body> <body>

View file

@ -15,7 +15,7 @@
<tr><td>Max image size</td><td><input type="text" name="maximagesize" value="4718592" /></td></tr> <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>Max pages</td><td><input type="text" name="maxpages" value="11" /></td></tr>
<tr><td>Default style</td><td><select name="defaultstyle" selected=""> <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> </select></td></tr>
<tr><td>Locked</td><td><input type="checkbox" name="locked" {{if $.board.Locked}}checked{{end}}/></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> <tr><td>Forced anonymity</td><td><input type="checkbox" name="forcedanon" {{if .board.ForcedAnon}}checked{{end}}/></td></tr>

View file

@ -1,22 +1,10 @@
<h2>Config editor</h2> <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"> <form action="/manage?action=config" method="POST">
<table> <input name="do" value="save" type="hidden" />
<tr><th>Name</th><th>Value</th><th>Description</th></tr> {{generateConfigTable}}<br />
<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 type="submit" /> <input type="submit" />
</form> </form>

View file

@ -1,10 +1,10 @@
<title>Gochan Manage page</title> <title>Gochan Manage page</title>
<link rel="stylesheet" href="/css/global/manage.css" /> <link rel="stylesheet" href="/css/global/manage.css" />
{{range $i, $style := .Styles_img}} {{range $i, $style := .Styles}}
<link rel="{{if not (isStyleDefault_img $style)}}alternate {{end}}stylesheet" href="/css/{{$style}}/manage.css" />{{end}} <link rel="{{if not (isStyleDefault $style)}}alternate {{end}}stylesheet" href="/css/{{$style}}/manage.css" />{{end}}
<link rel="shortcut icon" href="/favicon.png" /> <link rel="shortcut icon" href="/favicon.png" />
<script type="text/javascript"> <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}}" var webroot = "{{.SiteWebfolder}}"
</script> </script>
<script type="text/javascript" src="/javascript/jquery-3.3.1.min.js"></script> <script type="text/javascript" src="/javascript/jquery-3.3.1.min.js"></script>

View file

@ -6,14 +6,14 @@
<title>Edit Post</title> <title>Edit Post</title>
<script type="text/javascript" src="/javascript/jquery-3.3.1.min.js"></script> <script type="text/javascript" src="/javascript/jquery-3.3.1.min.js"></script>
<script type="text/javascript"> <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 webroot = "{{$.config.SiteWebfolder}}";
</script> </script>
<script type="text/javascript" src="/javascript/gochan.js"></script> <script type="text/javascript" src="/javascript/gochan.js"></script>
<script type="text/javascript" src="/javascript/manage.js"></script> <script type="text/javascript" src="/javascript/manage.js"></script>
<link rel="stylesheet" href="/css/global/img.css" /> <link rel="stylesheet" href="/css/global/img.css" />
{{range $_, $style := .config.Styles_img}} {{range $_, $style := .config.Styles}}
<link rel="{{if not (isStyleDefault_img $style)}}alternate {{end}}stylesheet" href="/css/{{$style}}/img.css" />{{end}} <link rel="{{if not (isStyleDefault $style)}}alternate {{end}}stylesheet" href="/css/{{$style}}/img.css" />{{end}}
<link rel="shortcut icon" href="/favicon.png" /> <link rel="shortcut icon" href="/favicon.png" />
</head> </head>
<body> <body>

View file

@ -1 +1 @@
1.10.3 1.11.0