more cleanup

This commit is contained in:
a 2022-02-22 05:49:08 +00:00
parent 77d85278a2
commit 9970be19e5
34 changed files with 0 additions and 2228 deletions

View File

@ -1 +0,0 @@
This file is here so that Git will include this folder in the repository.

View File

@ -1 +0,0 @@
This file is here so that Git will include this folder in the repository.

View File

@ -1,35 +0,0 @@
{
"config.DefaultGroup": 3,
"config.ActivationGroup": 5,
"staff_css": " background-color: #ffeaff;",
"uncategorised_forum_visible": true,
"site.EnableEmails": false,
"config.SmtpServer": "",
"config.ItemsPerPage": 40,
"db": {
"Host": "127.0.0.1",
"Username": "root",
"Password": "password",
"Dbname": "gosora",
"Port": "3306"
},
"site":
{
"Url": "localhost:8080",
"Port": "8080",
"EnableSsl": false
},
"config":
{
"SslPrivkey": "",
"SslFullchain": ""
},
"dev":
{
"debug": false
},
}

View File

@ -1,76 +0,0 @@
package main
import (
"fmt"
"math/bits"
"sync/atomic"
"unsafe"
)
const debug = true
type TreeCounterNode struct {
Value uint64
Zero *TreeCounterNode
One *TreeCounterNode
Parent *TreeCounterNode
}
// MEGA EXPERIMENTAL. Start from the right-most bits in the integer and move leftwards
type TreeTopicViewCounter struct {
root *TreeCounterNode
}
func newTreeTopicViewCounter() *TreeTopicViewCounter {
return &TreeTopicViewCounter{
&TreeCounterNode{0, nil, nil, nil},
}
}
func (counter *TreeTopicViewCounter) Bump(signTopicID int64) {
var topicID uint64 = uint64(signTopicID)
var zeroCount = bits.LeadingZeros64(topicID)
if debug {
fmt.Printf("topicID int64: %d\n", signTopicID)
fmt.Printf("topicID int64: %x\n", signTopicID)
fmt.Printf("topicID int64: %b\n", signTopicID)
fmt.Printf("topicID uint64: %b\n", topicID)
fmt.Printf("leading zeroes: %d\n", zeroCount)
var leadingZeroes = ""
for i := 0; i < zeroCount; i++ {
leadingZeroes += "0"
}
fmt.Printf("topicID lead uint64: %s%b\n", leadingZeroes, topicID)
fmt.Printf("---\n")
}
var stopAt uint64 = 64 - uint64(zeroCount)
var spot uint64 = 1
var node = counter.root
for {
if debug {
fmt.Printf("spot: %d\n", spot)
fmt.Printf("topicID&spot: %d\n", topicID&spot)
}
if topicID&spot == 1 {
if node.One == nil {
atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(node.One)), nil, unsafe.Pointer(&TreeCounterNode{0, nil, nil, node}))
}
node = node.One
} else {
if node.Zero == nil {
atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(node.Zero)), nil, unsafe.Pointer(&TreeCounterNode{0, nil, nil, node}))
}
node = node.Zero
}
spot++
if spot >= stopAt {
break
}
}
atomic.AddUint64(&node.Value, 1)
}

View File

@ -1,24 +0,0 @@
package main
import (
"log"
"testing"
)
func TestCounter(t *testing.T) {
counter := newTreeTopicViewCounter()
counter.Bump(1)
counter.Bump(57)
counter.Bump(58)
counter.Bump(59)
counter.Bump(9)
}
func TestScope(t *testing.T) {
var outVar int
closureHolder := func() {
outVar = 2
}
closureHolder()
log.Print("outVar: ", outVar)
}

View File

@ -1,3 +0,0 @@
/* Copyright Azareal 2016 - 2017 */
package main

View File

@ -1,3 +0,0 @@
/* Copyright Azareal 2016 - 2017 */
package main

View File

@ -1,14 +0,0 @@
<div class="rowitem passive deletable_block editable_parent post_item" style="background-color: #eaeaea;padding-top: 3px;padding-left: 4px;clear: both;border-bottom: solid 1px #ccc;padding-right: 3px;padding-bottom: 6px;">
<div class="userinfo" style="background: white;width: 132px;padding: 2px;margin-top: 2px;float: left;">
<div class="avatar_item" style="background-image: url(/uploads/avatar_1.jpg), url(/s/white-dot.jpg);background-position:0px -10px;background-repeat:no-repeat, repeat-y;background-size:128px;width:128px;height:100%;min-height: 128px;border-style:solid;border-color:#eaeaea;border-width:1px;">&nbsp;</div>
<div class="the_name" style="margin-top: 3px;text-align: center;color: #505050;">Azareal</div>
</div>
<div class="content_container" style="background:white;margin-left:137px;min-height:128px;margin-bottom:0;margin-right:3px;">
<div class="editable_block user_content" style="padding: 5px;margin-top: 3px;margin-bottom: 0;background: white;min-height: 133px;padding-bottom: 0;width: 100%;">boo</div>
<div class="button_container" style="border-top: solid 1px #eaeaea;border-spacing: 0px;border-collapse: collapse;padding: 0;margin: 0;display: block;">
<a style="border-right: solid 1px #eaeaea;color: #505050;font-size: 13px;padding-left: 5px;padding-right: 5px;">Edit</a>
<a style="border-right: solid 1px #eaeaea;color: #505050;font-size: 13px;padding-left: 0;padding-right: 5px;">Delete</a>
<a style="border: none;border-right: solid 1px #eaeaea;padding-right: 6px;color: #505050;font-size: 13px;">Report</a>
</div>
</div>
</div>

View File

@ -1,17 +0,0 @@
@echo off
echo Updating the dependencies
go get
if %errorlevel% neq 0 (
pause
exit /b %errorlevel%
)
echo Building the updater
go generate
go build -ldflags="-s -w" ./updater
if %errorlevel% neq 0 (
pause
exit /b %errorlevel%
)
updater.exe

View File

@ -1,22 +0,0 @@
package main
import (
c "git.tuxpa.in/a/gosora/common"
"github.com/oschwald/geoip2-golang"
)
var geoipDB *geoip2.Reader
var geoipDBLocation = "geoip_db.mmdb"
func init() {
c.Plugins.Add(&c.Plugin{UName: "geoip", Name: "Geoip", Author: "Azareal", Init: initGeoip, Deactivate: deactivateGeoip})
}
func initGeoip(plugin *c.Plugin) (err error) {
geoipDB, err = geoip2.Open(geoipDBLocation)
return err
}
func deactivateGeoip(plugin *c.Plugin) {
geoipDB.Close()
}

View File

@ -1,71 +0,0 @@
package main
import (
"errors"
"io"
"os/exec"
"runtime"
c "git.tuxpa.in/a/gosora/common"
)
/*
Sending emails in a way you really shouldn't be sending them.
This method doesn't require a SMTP server, but has higher chances of an email being rejected or being seen as spam. Use at your own risk. Only for Linux as Windows doesn't have Sendmail.
*/
func init() {
// Don't bother registering this plugin on platforms other than Linux
if runtime.GOOS != "linux" {
return
}
c.Plugins.Add(&c.Plugin{UName: "sendmail", Name: "Sendmail", Author: "Azareal", URL: "http://github.com/Azareal", Tag: "Linux Only", Init: initSendmail, Activate: activateSendmail, Deactivate: deactivateSendmail})
}
func initSendmail(plugin *c.Plugin) error {
plugin.AddHook("email_send_intercept", sendSendmail)
return nil
}
// /usr/sbin/sendmail is only available on Linux
func activateSendmail(plugin *c.Plugin) error {
if !c.Site.EnableEmails {
return errors.New("You have emails disabled in your configuration file")
}
if runtime.GOOS != "linux" {
return errors.New("This plugin only supports Linux")
}
return nil
}
func deactivateSendmail(plugin *c.Plugin) {
plugin.RemoveHook("email_send_intercept", sendSendmail)
}
func sendSendmail(data ...interface{}) interface{} {
to := data[0].(string)
subject := data[1].(string)
body := data[2].(string)
msg := "From: " + c.Site.Email + "\n"
msg += "To: " + to + "\n"
msg += "Subject: " + subject + "\n\n"
msg += body + "\n"
sendmail := exec.Command("/usr/sbin/sendmail", "-t", "-i")
stdin, err := sendmail.StdinPipe()
if err != nil {
return err // Possibly disable the plugin and show an error to the admin on the dashboard? Plugin log file?
}
err = sendmail.Start()
if err != nil {
return err
}
io.WriteString(stdin, msg)
err = stdin.Close()
if err != nil {
return err
}
return sendmail.Wait()
}

View File

@ -1,19 +0,0 @@
{
"Name": "tempra-simple",
"FriendlyName": "Tempra Simple",
"Version": "0.0.1",
"Creator": "Azareal",
"Settings": {
"PostLayout": {
"FriendlyName":"Post Layout",
"Options": ["Compact","Alternate"]
}
},
"Templates": [
{
"Name": "topic",
"Source": "topic_alt",
"When": "PostLayout=Alternate"
}
]
}

View File

@ -1,18 +0,0 @@
<?xml version="1.0" ?>
<theme>
<name>tempra-simple</name>
<friendlyName>Tempra Simple</friendlyName>
<version>0.0.1</version>
<creator url="http://github.com/Azareal">Azareal</creator>
<settings>
<name>PostLayout</name>
<friendlyName>Post Layout</name>
<options>
<option>Compact</option>
<option>Alternate</option>
</options>
</settings>
<templates>
<template name="topic" src="topic_alt" when="PostLayout=Alternate"></template>
</templates>
</theme>

View File

@ -1,15 +0,0 @@
package adventure
// We're experimenting with struct tags here atm
type Adventure struct {
ID int `schema:"name=aid;primary;auto"`
Name string `schema:"name=name;type=short_text"`
Desc string `schema:"name=desc;type=text"`
CreatedBy int `schema:"name=createdBy"`
//CreatedBy int `schema:"name=createdBy;relatesTo=users.uid"`
}
// TODO: Should we add a table interface?
func (a *Adventure) GetTable() string {
return "adventure"
}

View File

@ -1,8 +0,0 @@
package adventure
type AdventureStore interface {
Create() (int, error)
}
type DefaultAdventureStore struct {
}

View File

@ -1,7 +0,0 @@
{
"UName":"adventure",
"Name":"Adventure",
"Author":"Azareal",
"URL":"https://git.tuxpa.in/a/gosora",
"Skip":true
}

View File

@ -1 +0,0 @@
This file is here so that Git will include this folder in the repository.

View File

@ -1 +0,0 @@
package extend

View File

@ -1,47 +0,0 @@
package guilds
import (
"database/sql"
qgen "git.tuxpa.in/a/gosora/query_gen"
)
var Gstore GuildStore
type GuildStore interface {
Get(id int) (g *Guild, err error)
Create(name, desc string, active bool, privacy, uid, fid int) (int, error)
}
type SQLGuildStore struct {
get *sql.Stmt
create *sql.Stmt
}
func NewSQLGuildStore() (*SQLGuildStore, error) {
acc := qgen.NewAcc()
return &SQLGuildStore{
get: acc.Select("guilds").Columns("name, desc, active, privacy, joinable, owner, memberCount, mainForum, backdrop, createdAt, lastUpdateTime").Where("guildID=?").Prepare(),
create: acc.Insert("guilds").Columns("name, desc, active, privacy, joinable, owner, memberCount, mainForum, backdrop, createdAt, lastUpdateTime").Fields("?,?,?,?,1,?,1,?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()").Prepare(),
}, acc.FirstError()
}
func (s *SQLGuildStore) Close() {
_ = s.get.Close()
_ = s.create.Close()
}
func (s *SQLGuildStore) Get(id int) (g *Guild, err error) {
g = &Guild{ID: id}
err = s.get.QueryRow(id).Scan(&g.Name, &g.Desc, &g.Active, &g.Privacy, &g.Joinable, &g.Owner, &g.MemberCount, &g.MainForumID, &g.Backdrop, &g.CreatedAt, &g.LastUpdateTime)
return g, err
}
func (s *SQLGuildStore) Create(name, desc string, active bool, privacy, uid, fid int) (int, error) {
res, err := s.create.Exec(name, desc, active, privacy, uid, fid)
if err != nil {
return 0, err
}
lastID, err := res.LastInsertId()
return int(lastID), err
}

View File

@ -1,498 +0,0 @@
package guilds // import "git.tuxpa.in/a/gosora/extend/guilds/lib"
import (
"bytes"
"context"
"database/sql"
"errors"
"html/template"
"net/http"
"strconv"
"strings"
c "git.tuxpa.in/a/gosora/common"
"git.tuxpa.in/a/gosora/routes"
)
// A blank list to fill out that parameter in Page for routes which don't use it
var tList []interface{}
var ListStmt *sql.Stmt
var MemberListStmt *sql.Stmt
var MemberListJoinStmt *sql.Stmt
var GetMemberStmt *sql.Stmt
var AttachForumStmt *sql.Stmt
var UnattachForumStmt *sql.Stmt
var AddMemberStmt *sql.Stmt
// Guild is a struct representing a guild
type Guild struct {
ID int
Link string
Name string
Desc string
Active bool
Privacy int /* 0: Public, 1: Protected, 2: Private */
// Who should be able to accept applications and create invites? Mods+ or just admins? Mods is a good start, we can ponder over whether we should make this more flexible in the future.
Joinable int /* 0: Private, 1: Anyone can join, 2: Applications, 3: Invite-only */
MemberCount int
Owner int
Backdrop string
CreatedAt string
LastUpdateTime string
MainForumID int
MainForum *c.Forum
Forums []*c.Forum
ExtData c.ExtData
}
type Page struct {
Title string
Header *c.Header
ItemList []*c.TopicsRow
Forum *c.Forum
Guild *Guild
Page int
LastPage int
}
// ListPage is a page struct for constructing a list of every guild
type ListPage struct {
Title string
User *c.User
Header *c.Header
GuildList []*Guild
}
type MemberListPage struct {
Title string
Header *c.Header
ItemList []Member
Guild *Guild
Page int
LastPage int
}
// Member is a struct representing a specific member of a guild, not to be confused with the global User struct.
type Member struct {
Link string
Rank int /* 0: Member. 1: Mod. 2: Admin. */
RankString string /* Member, Mod, Admin, Owner */
PostCount int
JoinedAt string
Offline bool // TODO: Need to track the online states of members when WebSockets are enabled
User c.User
}
func PrebuildTmplList(user *c.User, h *c.Header) c.CTmpl {
guildList := []*Guild{
&Guild{
ID: 1,
Name: "lol",
Link: BuildGuildURL(c.NameToSlug("lol"), 1),
Desc: "A group for people who like to laugh",
Active: true,
MemberCount: 1,
Owner: 1,
CreatedAt: "date",
LastUpdateTime: "date",
MainForumID: 1,
MainForum: c.Forums.DirtyGet(1),
Forums: []*c.Forum{c.Forums.DirtyGet(1)},
},
}
listPage := ListPage{"Guild List", user, h, guildList}
return c.CTmpl{"guilds_guild_list", "guilds_guild_list.html", "templates/", "guilds.ListPage", listPage, []string{"./extend/guilds/lib"}}
}
// TODO: Do this properly via the widget system
// TODO: REWRITE THIS
func CommonAreaWidgets(header *c.Header) {
// TODO: Hot Groups? Featured Groups? Official Groups?
var b bytes.Buffer
menu := c.WidgetMenu{"Guilds", []c.WidgetMenuItem{
c.WidgetMenuItem{"Create Guild", "/guild/create/", false},
}}
err := header.Theme.RunTmpl("widget_menu", pi, w)
if err != nil {
c.LogError(err)
return
}
if header.Theme.HasDock("leftSidebar") {
header.Widgets.LeftSidebar = template.HTML(string(b.Bytes()))
} else if header.Theme.HasDock("rightSidebar") {
header.Widgets.RightSidebar = template.HTML(string(b.Bytes()))
}
}
// TODO: Do this properly via the widget system
// TODO: Make a better more customisable group widget system
func GuildWidgets(header *c.Header, guildItem *Guild) (success bool) {
return false // Disabled until the next commit
/*var b bytes.Buffer
var menu WidgetMenu = WidgetMenu{"Guild Options", []WidgetMenuItem{
WidgetMenuItem{"Join", "/guild/join/" + strconv.Itoa(guildItem.ID), false},
WidgetMenuItem{"Members", "/guild/members/" + strconv.Itoa(guildItem.ID), false},
}}
err := templates.ExecuteTemplate(&b, "widget_menu.html", menu)
if err != nil {
c.LogError(err)
return false
}
if themes[header.Theme.Name].Sidebars == "left" {
header.Widgets.LeftSidebar = template.HTML(string(b.Bytes()))
} else if themes[header.Theme.Name].Sidebars == "right" || themes[header.Theme.Name].Sidebars == "both" {
header.Widgets.RightSidebar = template.HTML(string(b.Bytes()))
} else {
return false
}
return true*/
}
/*
Custom Pages
*/
func RouteGuildList(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError {
h, ferr := c.UserCheck(w, r, user)
if ferr != nil {
return ferr
}
CommonAreaWidgets(h)
rows, err := ListStmt.Query()
if err != nil && err != c.ErrNoRows {
return c.InternalError(err, w, r)
}
defer rows.Close()
var guildList []*Guild
for rows.Next() {
g := &Guild{ID: 0}
err := rows.Scan(&g.ID, &g.Name, &g.Desc, &g.Active, &g.Privacy, &g.Joinable, &g.Owner, &g.MemberCount, &g.CreatedAt, &g.LastUpdateTime)
if err != nil {
return c.InternalError(err, w, r)
}
g.Link = BuildGuildURL(c.NameToSlug(g.Name), g.ID)
guildList = append(guildList, g)
}
if err = rows.Err(); err != nil {
return c.InternalError(err, w, r)
}
pi := ListPage{"Guild List", user, h, guildList}
return routes.RenderTemplate("guilds_guild_list", w, r, h, pi)
}
func MiddleViewGuild(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError {
_, guildID, err := routes.ParseSEOURL(r.URL.Path[len("/guild/"):])
if err != nil {
return c.PreError("Not a valid guild ID", w, r)
}
guildItem, err := Gstore.Get(guildID)
if err != nil {
return c.LocalError("Bad guild", w, r, user)
}
// TODO: Build and pass header
if !guildItem.Active {
return c.NotFound(w, r, nil)
}
return nil
// TODO: Re-implement this
// Re-route the request to routeForums
//var ctx = context.WithValue(r.Context(), "guilds_current_guild", guildItem)
//return routeForum(w, r.WithContext(ctx), user, strconv.Itoa(guildItem.MainForumID))
}
func RouteCreateGuild(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError {
h, ferr := c.UserCheck(w, r, user)
if ferr != nil {
return ferr
}
h.Title = "Create Guild"
// TODO: Add an approval queue mode for group creation
if !user.Loggedin || !user.PluginPerms["CreateGuild"] {
return c.NoPermissions(w, r, user)
}
CommonAreaWidgets(h)
return routes.RenderTemplate("guilds_create_guild", w, r, h, c.Page{h, tList, nil})
}
func RouteCreateGuildSubmit(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError {
// TODO: Add an approval queue mode for group creation
if !user.Loggedin || !user.PluginPerms["CreateGuild"] {
return c.NoPermissions(w, r, user)
}
guildActive := true
guildName := c.SanitiseSingleLine(r.PostFormValue("group_name"))
// TODO: Allow Markdown / BBCode / Limited HTML in the description?
guildDesc := c.SanitiseBody(r.PostFormValue("group_desc"))
var guildPrivacy int
switch r.PostFormValue("group_privacy") {
case "0":
guildPrivacy = 0 // Public
case "1":
guildPrivacy = 1 // Protected
case "2":
guildPrivacy = 2 // private
default:
guildPrivacy = 0
}
// Create the backing forum
fid, err := c.Forums.Create(guildName, "", true, "")
if err != nil {
return c.InternalError(err, w, r)
}
gid, err := Gstore.Create(guildName, guildDesc, guildActive, guildPrivacy, user.ID, fid)
if err != nil {
return c.InternalError(err, w, r)
}
// Add the main backing forum to the forum list
err = AttachForum(gid, fid)
if err != nil {
return c.InternalError(err, w, r)
}
_, err = AddMemberStmt.Exec(gid, user.ID, 2)
if err != nil {
return c.InternalError(err, w, r)
}
http.Redirect(w, r, BuildGuildURL(c.NameToSlug(guildName), gid), http.StatusSeeOther)
return nil
}
func RouteMemberList(w http.ResponseWriter, r *http.Request, user *c.User) c.RouteError {
header, ferr := c.UserCheck(w, r, &user)
if ferr != nil {
return ferr
}
_, guildID, err := routes.ParseSEOURL(r.URL.Path[len("/guild/members/"):])
if err != nil {
return c.PreError("Not a valid group ID", w, r)
}
guild, err := Gstore.Get(guildID)
if err != nil {
return c.LocalError("Bad group", w, r, user)
}
guild.Link = BuildGuildURL(c.NameToSlug(guild.Name), guild.ID)
GuildWidgets(header, guild)
rows, err := MemberListJoinStmt.Query(guildID)
if err != nil && err != c.ErrNoRows {
return c.InternalError(err, w, r)
}
var guildMembers []Member
for rows.Next() {
gMember := Member{PostCount: 0}
err := rows.Scan(&gMember.User.ID, &gMember.Rank, &gMember.PostCount, &gMember.JoinedAt, &gMember.User.Name, &gMember.User.RawAvatar)
if err != nil {
return c.InternalError(err, w, r)
}
gMember.Link = c.BuildProfileURL(c.NameToSlug(gMember.User.Name), gMember.User.ID)
gMember.User.Avatar, gMember.User.MicroAvatar = c.BuildAvatar(gMember.User.ID, gMember.User.RawAvatar)
gMember.JoinedAt, _ = c.RelativeTimeFromString(gMember.JoinedAt)
if guild.Owner == gMember.User.ID {
gMember.RankString = "Owner"
} else {
switch gMember.Rank {
case 0:
gMember.RankString = "Member"
case 1:
gMember.RankString = "Mod"
case 2:
gMember.RankString = "Admin"
}
}
guildMembers = append(guildMembers, gMember)
}
if err = rows.Err(); err != nil {
return c.InternalError(err, w, r)
}
rows.Close()
pi := MemberListPage{"Guild Member List", user, header, gMembers, guild, 0, 0}
// A plugin with plugins. Pluginception!
if c.RunPreRenderHook("pre_render_guilds_member_list", w, r, user, &pi) {
return nil
}
err = c.RunThemeTemplate(header.Theme.Name, "guilds_member_list", pi, w)
if err != nil {
return c.InternalError(err, w, r)
}
return nil
}
func AttachForum(guildID, fid int) error {
_, err := AttachForumStmt.Exec(guildID, fid)
return err
}
func UnattachForum(fid int) error {
_, err := AttachForumStmt.Exec(fid)
return err
}
func BuildGuildURL(slug string, id int) string {
if slug == "" || !c.Config.BuildSlugs {
return "/guild/" + strconv.Itoa(id)
}
return "/guild/" + slug + "." + strconv.Itoa(id)
}
/*
Hooks
*/
// TODO: Prebuild this template
func PreRenderViewForum(w http.ResponseWriter, r *http.Request, user *c.User, data interface{}) (halt bool) {
pi := data.(*c.ForumPage)
if pi.Header.ExtData.Items != nil {
if guildData, ok := pi.Header.ExtData.Items["guilds_current_group"]; ok {
guildItem := guildData.(*Guild)
guildpi := Page{pi.Title, pi.Header, pi.ItemList, pi.Forum, guildItem, pi.Page, pi.LastPage}
err := routes.RenderTemplate("guilds_view_guild", w, r, pi.Header, guildpi)
if err != nil {
c.LogError(err)
return false
}
return true
}
}
return false
}
func TrowAssign(args ...interface{}) interface{} {
var forum = args[1].(*c.Forum)
if forum.ParentType == "guild" {
var topicItem = args[0].(*c.TopicsRow)
topicItem.ForumLink = "/guild/" + strings.TrimPrefix(topicItem.ForumLink, c.GetForumURLPrefix())
}
return nil
}
// TODO: It would be nice, if you could select one of the boards in the group from that drop-down rather than just the one you got linked from
func TopicCreatePreLoop(args ...interface{}) interface{} {
var fid = args[2].(int)
if c.Forums.DirtyGet(fid).ParentType == "guild" {
var strictmode = args[5].(*bool)
*strictmode = true
}
return nil
}
// TODO: Add privacy options
// TODO: Add support for multiple boards and add per-board simplified permissions
// TODO: Take js into account for routes which expect JSON responses
func ForumCheck(args ...interface{}) (skip bool, rerr c.RouteError) {
r := args[1].(*http.Request)
fid := args[3].(*int)
forum := c.Forums.DirtyGet(*fid)
if forum.ParentType == "guild" {
var err error
w := args[0].(http.ResponseWriter)
guildItem, ok := r.Context().Value("guilds_current_group").(*Guild)
if !ok {
guildItem, err = Gstore.Get(forum.ParentID)
if err != nil {
return true, c.InternalError(errors.New("Unable to find the parent group for a forum"), w, r)
}
if !guildItem.Active {
return true, c.NotFound(w, r, nil) // TODO: Can we pull header out of args?
}
r = r.WithContext(context.WithValue(r.Context(), "guilds_current_group", guildItem))
}
user := args[2].(*c.User)
var rank, posts int
var joinedAt string
// TODO: Group privacy settings. For now, groups are all globally visible
// Clear the default group permissions
// TODO: Do this more efficiently, doing it quick and dirty for now to get this out quickly
c.OverrideForumPerms(&user.Perms, false)
user.Perms.ViewTopic = true
err = GetMemberStmt.QueryRow(guildItem.ID, user.ID).Scan(&rank, &posts, &joinedAt)
if err != nil && err != c.ErrNoRows {
return true, c.InternalError(err, w, r)
} else if err != nil {
// TODO: Should we let admins / guests into public groups?
return true, c.LocalError("You're not part of this group!", w, r, user)
}
// TODO: Implement bans properly by adding the Local Ban API in the next commit
// TODO: How does this even work? Refactor it along with the rest of this plugin!
if rank < 0 {
return true, c.LocalError("You've been banned from this group!", w, r, user)
}
// Basic permissions for members, more complicated permissions coming in the next commit!
if guildItem.Owner == user.ID {
c.OverrideForumPerms(&user.Perms, true)
} else if rank == 0 {
user.Perms.LikeItem = true
user.Perms.CreateTopic = true
user.Perms.CreateReply = true
} else {
c.OverrideForumPerms(&user.Perms, true)
}
return true, nil
}
return false, nil
}
// TODO: Override redirects? I don't think this is needed quite yet
func Widgets(args ...interface{}) interface{} {
zone := args[0].(string)
h := args[2].(*c.Header)
request := args[3].(*http.Request)
if zone != "view_forum" {
return false
}
f := args[1].(*c.Forum)
if f.ParentType == "guild" {
// This is why I hate using contexts, all the daisy chains and interface casts x.x
guild, ok := request.Context().Value("guilds_current_group").(*Guild)
if !ok {
c.LogError(errors.New("Unable to find a parent group in the context data"))
return false
}
if h.ExtData.Items == nil {
h.ExtData.Items = make(map[string]interface{})
}
h.ExtData.Items["guilds_current_group"] = guild
return GuildWidgets(h, guild)
}
return false
}

View File

@ -1,7 +0,0 @@
{
"UName":"guilds",
"Name":"Guilds",
"Author":"Azareal",
"URL":"https://git.tuxpa.in/a/gosora",
"Skip":true
}

View File

@ -1,131 +0,0 @@
package main
import (
c "git.tuxpa.in/a/gosora/common"
guilds "git.tuxpa.in/a/gosora/extend/guilds/lib"
)
// TODO: Add a better way of splitting up giant plugins like this
// TODO: Add a plugin interface instead of having a bunch of argument to AddPlugin?
func init() {
c.Plugins.Add(&c.Plugin{UName: "guilds", Name: "Guilds", Author: "Azareal", URL: "https://github.com/Azareal", Init: initGuilds, Deactivate: deactivateGuilds, Install: installGuilds})
// TODO: Is it possible to avoid doing this when the plugin isn't activated?
c.PrebuildTmplList = append(c.PrebuildTmplList, guilds.PrebuildTmplList)
}
func initGuilds(pl *c.Plugin) (err error) {
pl.AddHook("intercept_build_widgets", guilds.Widgets)
pl.AddHook("trow_assign", guilds.TrowAssign)
pl.AddHook("topic_create_pre_loop", guilds.TopicCreatePreLoop)
pl.AddHook("pre_render_forum", guilds.PreRenderViewForum)
pl.AddHook("simple_forum_check_pre_perms", guilds.ForumCheck)
pl.AddHook("forum_check_pre_perms", guilds.ForumCheck)
// TODO: Auto-grant this perm to admins upon installation?
c.RegisterPluginPerm("CreateGuild")
router.HandleFunc("/guilds/", guilds.RouteGuildList)
router.HandleFunc("/guild/", guilds.MiddleViewGuild)
router.HandleFunc("/guild/create/", guilds.RouteCreateGuild)
router.HandleFunc("/guild/create/submit/", guilds.RouteCreateGuildSubmit)
router.HandleFunc("/guild/members/", guilds.RouteMemberList)
guilds.Gstore, err = guilds.NewSQLGuildStore()
if err != nil {
return err
}
acc := qgen.NewAcc()
guilds.ListStmt = acc.Select("guilds").Columns("guildID, name, desc, active, privacy, joinable, owner, memberCount, createdAt, lastUpdateTime").Prepare()
guilds.MemberListStmt = acc.Select("guilds_members").Columns("guildID, uid, rank, posts, joinedAt").Prepare()
guilds.MemberListJoinStmt = acc.SimpleLeftJoin("guilds_members", "users", "users.uid, guilds_members.rank, guilds_members.posts, guilds_members.joinedAt, users.name, users.avatar", "guilds_members.uid = users.uid", "guilds_members.guildID = ?", "guilds_members.rank DESC, guilds_members.joinedat ASC", "")
guilds.GetMemberStmt = acc.Select("guilds_members").Columns("rank, posts, joinedAt").Where("guildID = ? AND uid = ?").Prepare()
guilds.AttachForumStmt = acc.Update("forums").Set("parentID = ?, parentType = 'guild'").Where("fid = ?").Prepare()
guilds.UnattachForumStmt = acc.Update("forums").Set("parentID = 0, parentType = ''").Where("fid = ?").Prepare()
guilds.AddMemberStmt = acc.Insert("guilds_members").Columns("guildID, uid, rank, posts, joinedAt").Fields("?,?,?,0,UTC_TIMESTAMP()").Prepare()
return acc.FirstError()
}
func deactivateGuilds(pl *c.Plugin) {
pl.RemoveHook("intercept_build_widgets", guilds.Widgets)
pl.RemoveHook("trow_assign", guilds.TrowAssign)
pl.RemoveHook("topic_create_pre_loop", guilds.TopicCreatePreLoop)
pl.RemoveHook("pre_render_forum", guilds.PreRenderViewForum)
pl.RemoveHook("simple_forum_check_pre_perms", guilds.ForumCheck)
pl.RemoveHook("forum_check_pre_perms", guilds.ForumCheck)
c.DeregisterPluginPerm("CreateGuild")
_ = router.RemoveFunc("/guilds/")
_ = router.RemoveFunc("/guild/")
_ = router.RemoveFunc("/guild/create/")
_ = router.RemoveFunc("/guild/create/submit/")
_ = guilds.ListStmt.Close()
_ = guilds.MemberListStmt.Close()
_ = guilds.MemberListJoinStmt.Close()
_ = guilds.GetMemberStmt.Close()
_ = guilds.AttachForumStmt.Close()
_ = guilds.UnattachForumStmt.Close()
_ = guilds.AddMemberStmt.Close()
}
// TODO: Stop accessing the query builder directly and add a feature in Gosora which is more easily reversed, if an error comes up during the installation process
type tC = qgen.DBTableColumn
func installGuilds(plugin *c.Plugin) error {
guildTableStmt, err := qgen.Builder.CreateTable("guilds", "utf8mb4", "utf8mb4_general_ci",
[]tC{
tC{"guildID", "int", 0, false, true, ""},
tC{"name", "varchar", 100, false, false, ""},
tC{"desc", "varchar", 200, false, false, ""},
tC{"active", "boolean", 1, false, false, ""},
tC{"privacy", "smallint", 0, false, false, ""},
tC{"joinable", "smallint", 0, false, false, "0"},
tC{"owner", "int", 0, false, false, ""},
tC{"memberCount", "int", 0, false, false, ""},
tC{"mainForum", "int", 0, false, false, "0"}, // The board the user lands on when they click on a group, we'll make it possible for group admins to change what users land on
//tC{"boards","varchar",255,false,false,""}, // Cap the max number of boards at 8 to avoid overflowing the confines of a 64-bit integer?
tC{"backdrop", "varchar", 200, false, false, ""}, // File extension for the uploaded file, or an external link
tC{"createdAt", "createdAt", 0, false, false, ""},
tC{"lastUpdateTime", "datetime", 0, false, false, ""},
},
[]qgen.DBTableKey{
qgen.DBTableKey{"guildID", "primary"},
},
)
if err != nil {
return err
}
_, err = guildTableStmt.Exec()
if err != nil {
return err
}
guildMembersTableStmt, err := qgen.Builder.CreateTable("guilds_members", "", "",
[]tC{
tC{"guildID", "int", 0, false, false, ""},
tC{"uid", "int", 0, false, false, ""},
tC{"rank", "int", 0, false, false, "0"}, /* 0: Member. 1: Mod. 2: Admin. */
tC{"posts", "int", 0, false, false, "0"}, /* Per-Group post count. Should we do some sort of score system? */
tC{"joinedAt", "datetime", 0, false, false, ""},
}, nil,
)
if err != nil {
return err
}
_, err = guildMembersTableStmt.Exec()
return err
}
// TO-DO; Implement an uninstallation system into Gosora. And a better installation system.
func uninstallGuilds(plugin *c.Plugin) error {
return nil
}

View File

@ -1 +0,0 @@
This file is here so that Git will include this folder in the repository.

View File

@ -1,5 +0,0 @@
current_page.test = true;
// This shouldn't ever fail
var errmsg = "gotcha";
errmsg;

View File

@ -1,7 +0,0 @@
{
"UName":"heytherejs",
"Name":"HeythereJS",
"Author":"Azareal",
"URL":"https://git.tuxpa.in/a/gosora",
"Main":"main.js"
}

View File

@ -1,29 +0,0 @@
// WIP - Experimental adventure plugin, this might find a new home soon, but it's here to stress test Gosora's extensibility for now
package extend
import c "git.tuxpa.in/a/gosora/common"
func init() {
c.Plugins.Add(&c.Plugin{
UName: "adventure",
Name: "Adventure",
Tag: "WIP",
Author: "Azareal",
URL: "https://github.com/Azareal",
Init: initAdventure,
Deactivate: deactivateAdventure,
Install: installAdventure,
})
}
func initAdventure(pl *c.Plugin) error {
return nil
}
// TODO: Change the signature to return an error?
func deactivateAdventure(pl *c.Plugin) {
}
func installAdventure(pl *c.Plugin) error {
return nil
}

View File

@ -1,422 +0,0 @@
package extend
import (
"bytes"
"math/rand"
"regexp"
"strconv"
"time"
c "git.tuxpa.in/a/gosora/common"
)
var bbcodeRandom *rand.Rand
var bbcodeInvalidNumber []byte
var bbcodeNoNegative []byte
var bbcodeMissingTag []byte
var bbcodeBold *regexp.Regexp
var bbcodeItalic *regexp.Regexp
var bbcodeUnderline *regexp.Regexp
var bbcodeStrike *regexp.Regexp
var bbcodeH1 *regexp.Regexp
var bbcodeURL *regexp.Regexp
var bbcodeURLLabel *regexp.Regexp
var bbcodeQuotes *regexp.Regexp
var bbcodeCode *regexp.Regexp
var bbcodeSpoiler *regexp.Regexp
func init() {
c.Plugins.Add(&c.Plugin{UName: "bbcode", Name: "BBCode", Author: "Azareal", URL: "https://github.com/Azareal", Init: InitBbcode, Deactivate: deactivateBbcode})
}
func InitBbcode(pl *c.Plugin) error {
bbcodeInvalidNumber = []byte("<red>[Invalid Number]</red>")
bbcodeNoNegative = []byte("<red>[No Negative Numbers]</red>")
bbcodeMissingTag = []byte("<red>[Missing Tag]</red>")
bbcodeBold = regexp.MustCompile(`(?s)\[b\](.*)\[/b\]`)
bbcodeItalic = regexp.MustCompile(`(?s)\[i\](.*)\[/i\]`)
bbcodeUnderline = regexp.MustCompile(`(?s)\[u\](.*)\[/u\]`)
bbcodeStrike = regexp.MustCompile(`(?s)\[s\](.*)\[/s\]`)
bbcodeH1 = regexp.MustCompile(`(?s)\[h1\](.*)\[/h1\]`)
urlpattern := `(http|https|ftp|mailto*)(:??)\/\/([\.a-zA-Z\/]+)`
bbcodeURL = regexp.MustCompile(`\[url\]` + urlpattern + `\[/url\]`)
bbcodeURLLabel = regexp.MustCompile(`(?s)\[url=` + urlpattern + `\](.*)\[/url\]`)
bbcodeQuotes = regexp.MustCompile(`\[quote\](.*)\[/quote\]`)
bbcodeCode = regexp.MustCompile(`\[code\](.*)\[/code\]`)
bbcodeSpoiler = regexp.MustCompile(`\[spoiler\](.*)\[/spoiler\]`)
bbcodeRandom = rand.New(rand.NewSource(time.Now().UnixNano()))
pl.AddHook("parse_assign", BbcodeFullParse)
pl.AddHook("topic_ogdesc_assign", BbcodeStripTags)
return nil
}
func deactivateBbcode(pl *c.Plugin) {
pl.RemoveHook("parse_assign", BbcodeFullParse)
pl.RemoveHook("topic_ogdesc_assign", BbcodeStripTags)
}
func BbcodeStripTags(msg string) string {
msg = bbcodeBold.ReplaceAllString(msg, "$1")
msg = bbcodeItalic.ReplaceAllString(msg, "$1")
msg = bbcodeUnderline.ReplaceAllString(msg, "$1")
msg = bbcodeStrike.ReplaceAllString(msg, "$1")
return msg
}
func BbcodeRegexParse(msg string) string {
msg = bbcodeBold.ReplaceAllString(msg, "<b>$1</b>")
msg = bbcodeItalic.ReplaceAllString(msg, "<i>$1</i>")
msg = bbcodeUnderline.ReplaceAllString(msg, "<u>$1</u>")
msg = bbcodeStrike.ReplaceAllString(msg, "<s>$1</s>")
msg = bbcodeURL.ReplaceAllString(msg, "<a href=''$1$2//$3' rel='ugc'>$1$2//$3</i>")
msg = bbcodeURLLabel.ReplaceAllString(msg, "<a href=''$1$2//$3' rel='ugc'>$4</i>")
msg = bbcodeQuotes.ReplaceAllString(msg, "<blockquote>$1</blockquote>")
msg = bbcodeSpoiler.ReplaceAllString(msg, "<spoiler>$1</spoiler>")
msg = bbcodeH1.ReplaceAllString(msg, "<h2>$1</h2>")
//msg = bbcodeCode.ReplaceAllString(msg,"<span class='codequotes'>$1</span>")
return msg
}
// Only does the simple BBCode like [u], [b], [i] and [s]
func bbcodeSimpleParse(msg string) string {
var hasU, hasB, hasI, hasS bool
mbytes := []byte(msg)
for i := 0; (i + 2) < len(mbytes); i++ {
if mbytes[i] == '[' && mbytes[i+2] == ']' {
ch := mbytes[i+1]
if ch == 'b' && !hasB {
mbytes[i] = '<'
mbytes[i+2] = '>'
hasB = true
} else if ch == 'i' && !hasI {
mbytes[i] = '<'
mbytes[i+2] = '>'
hasI = true
} else if ch == 'u' && !hasU {
mbytes[i] = '<'
mbytes[i+2] = '>'
hasU = true
} else if ch == 's' && !hasS {
mbytes[i] = '<'
mbytes[i+2] = '>'
hasS = true
}
i += 2
}
}
// There's an unclosed tag in there x.x
if hasI || hasU || hasB || hasS {
closeUnder := []byte("</u>")
closeItalic := []byte("</i>")
closeBold := []byte("</b>")
closeStrike := []byte("</s>")
if hasI {
mbytes = append(mbytes, closeItalic...)
}
if hasU {
mbytes = append(mbytes, closeUnder...)
}
if hasB {
mbytes = append(mbytes, closeBold...)
}
if hasS {
mbytes = append(mbytes, closeStrike...)
}
}
return string(mbytes)
}
// Here for benchmarking purposes. Might add a plugin setting for disabling [code] as it has it's paws everywhere
func BbcodeParseWithoutCode(msg string) string {
var hasU, hasB, hasI, hasS bool
var complexBbc bool
mbytes := []byte(msg)
for i := 0; (i + 3) < len(mbytes); i++ {
if mbytes[i] == '[' {
if mbytes[i+2] != ']' {
if mbytes[i+1] == '/' {
if mbytes[i+3] == ']' {
switch mbytes[i+2] {
case 'b':
mbytes[i] = '<'
mbytes[i+3] = '>'
hasB = false
case 'i':
mbytes[i] = '<'
mbytes[i+3] = '>'
hasI = false
case 'u':
mbytes[i] = '<'
mbytes[i+3] = '>'
hasU = false
case 's':
mbytes[i] = '<'
mbytes[i+3] = '>'
hasS = false
}
i += 3
} else {
complexBbc = true
}
} else {
complexBbc = true
}
} else {
ch := mbytes[i+1]
if ch == 'b' && !hasB {
mbytes[i] = '<'
mbytes[i+2] = '>'
hasB = true
} else if ch == 'i' && !hasI {
mbytes[i] = '<'
mbytes[i+2] = '>'
hasI = true
} else if ch == 'u' && !hasU {
mbytes[i] = '<'
mbytes[i+2] = '>'
hasU = true
} else if ch == 's' && !hasS {
mbytes[i] = '<'
mbytes[i+2] = '>'
hasS = true
}
i += 2
}
}
}
// There's an unclosed tag in there x.x
if hasI || hasU || hasB || hasS {
closeUnder := []byte("</u>")
closeItalic := []byte("</i>")
closeBold := []byte("</b>")
closeStrike := []byte("</s>")
if hasI {
mbytes = append(bytes.TrimSpace(mbytes), closeItalic...)
}
if hasU {
mbytes = append(bytes.TrimSpace(mbytes), closeUnder...)
}
if hasB {
mbytes = append(bytes.TrimSpace(mbytes), closeBold...)
}
if hasS {
mbytes = append(bytes.TrimSpace(mbytes), closeStrike...)
}
}
// Copy the new complex parser over once the rough edges have been smoothed over
if complexBbc {
msg = string(mbytes)
msg = bbcodeURL.ReplaceAllString(msg, "<a href='$1$2//$3' rel='ugc'>$1$2//$3</i>")
msg = bbcodeURLLabel.ReplaceAllString(msg, "<a href='$1$2//$3' rel='ugc'>$4</i>")
msg = bbcodeSpoiler.ReplaceAllString(msg, "<spoiler>$1</spoiler>")
msg = bbcodeQuotes.ReplaceAllString(msg, "<blockquote>$1</blockquote>")
return bbcodeCode.ReplaceAllString(msg, "<span class='codequotes'>$1</span>")
}
return string(mbytes)
}
// Does every type of BBCode
func BbcodeFullParse(msg string) string {
var hasU, hasB, hasI, hasS, hasC bool
var complexBbc bool
mbytes := []byte(msg)
mbytes = append(mbytes, c.SpaceGap...)
for i := 0; i < len(mbytes); i++ {
if mbytes[i] == '[' {
if mbytes[i+2] != ']' {
if mbytes[i+1] == '/' {
if mbytes[i+3] == ']' {
if !hasC {
switch mbytes[i+2] {
case 'b':
mbytes[i] = '<'
mbytes[i+3] = '>'
hasB = false
case 'i':
mbytes[i] = '<'
mbytes[i+3] = '>'
hasI = false
case 'u':
mbytes[i] = '<'
mbytes[i+3] = '>'
hasU = false
case 's':
mbytes[i] = '<'
mbytes[i+3] = '>'
hasS = false
}
i += 3
}
} else {
if mbytes[i+6] == ']' && mbytes[i+2] == 'c' && mbytes[i+3] == 'o' && mbytes[i+4] == 'd' && mbytes[i+5] == 'e' {
hasC = false
i += 7
}
complexBbc = true
}
} else {
// Put the biggest index first to avoid unnecessary bounds checks
if mbytes[i+5] == ']' && mbytes[i+1] == 'c' && mbytes[i+2] == 'o' && mbytes[i+3] == 'd' && mbytes[i+4] == 'e' {
hasC = true
i += 6
}
complexBbc = true
}
} else if !hasC {
ch := mbytes[i+1]
if ch == 'b' && !hasB {
mbytes[i] = '<'
mbytes[i+2] = '>'
hasB = true
} else if ch == 'i' && !hasI {
mbytes[i] = '<'
mbytes[i+2] = '>'
hasI = true
} else if ch == 'u' && !hasU {
mbytes[i] = '<'
mbytes[i+2] = '>'
hasU = true
} else if ch == 's' && !hasS {
mbytes[i] = '<'
mbytes[i+2] = '>'
hasS = true
}
i += 2
}
}
}
// There's an unclosed tag in there somewhere x.x
if hasI || hasU || hasB || hasS {
closeUnder := []byte("</u>")
closeItalic := []byte("</i>")
closeBold := []byte("</b>")
closeStrike := []byte("</s>")
if hasI {
mbytes = append(bytes.TrimSpace(mbytes), closeItalic...)
}
if hasU {
mbytes = append(bytes.TrimSpace(mbytes), closeUnder...)
}
if hasB {
mbytes = append(bytes.TrimSpace(mbytes), closeBold...)
}
if hasS {
mbytes = append(bytes.TrimSpace(mbytes), closeStrike...)
}
mbytes = append(mbytes, c.SpaceGap...)
}
if complexBbc {
i := 0
var start, lastTag int
var outbytes []byte
for ; i < len(mbytes); i++ {
if mbytes[i] == '[' {
if mbytes[i+1] == 'u' {
if mbytes[i+4] == ']' && mbytes[i+2] == 'r' && mbytes[i+3] == 'l' {
i, start, lastTag, outbytes = bbcodeParseURL(i, start, lastTag, mbytes, outbytes)
continue
}
} else if mbytes[i+1] == 'r' {
if bytes.Equal(mbytes[i+2:i+6], []byte("and]")) {
i, start, lastTag, outbytes = bbcodeParseRand(i, start, lastTag, mbytes, outbytes)
}
}
}
}
if lastTag != i {
outbytes = append(outbytes, mbytes[lastTag:]...)
}
if len(outbytes) != 0 {
msg = string(outbytes[0 : len(outbytes)-10])
} else {
msg = string(mbytes[0 : len(mbytes)-10])
}
// TODO: Optimise these
//msg = bbcode_url.ReplaceAllString(msg,"<a href=\"$1$2//$3\" rel=\"ugc\">$1$2//$3</i>")
msg = bbcodeURLLabel.ReplaceAllString(msg, "<a href='$1$2//$3' rel='ugc'>$4</i>")
msg = bbcodeQuotes.ReplaceAllString(msg, "<blockquote>$1</blockquote>")
msg = bbcodeCode.ReplaceAllString(msg, "<span class='codequotes'>$1</span>")
msg = bbcodeSpoiler.ReplaceAllString(msg, "<spoiler>$1</spoiler>")
msg = bbcodeH1.ReplaceAllString(msg, "<h2>$1</h2>")
} else {
msg = string(mbytes[0 : len(mbytes)-10])
}
return msg
}
// TODO: Strip the containing [url] so the media parser can work it's magic instead? Or do we want to allow something like [url=]label[/url] here?
func bbcodeParseURL(i int, start int, lastTag int, mbytes []byte, outbytes []byte) (int, int, int, []byte) {
start = i + 5
outbytes = append(outbytes, mbytes[lastTag:i]...)
i = start
i += c.PartialURLStringLen2(string(mbytes[start:]))
if !bytes.Equal(mbytes[i:i+6], []byte("[/url]")) {
outbytes = append(outbytes, c.InvalidURL...)
return i, start, lastTag, outbytes
}
outbytes = append(outbytes, c.URLOpen...)
outbytes = append(outbytes, mbytes[start:i]...)
outbytes = append(outbytes, c.URLOpen2...)
outbytes = append(outbytes, mbytes[start:i]...)
outbytes = append(outbytes, c.URLClose...)
i += 6
lastTag = i
return i, start, lastTag, outbytes
}
func bbcodeParseRand(i int, start int, lastTag int, msgbytes []byte, outbytes []byte) (int, int, int, []byte) {
outbytes = append(outbytes, msgbytes[lastTag:i]...)
start = i + 6
i = start
for ; ; i++ {
if msgbytes[i] == '[' {
if !bytes.Equal(msgbytes[i+1:i+7], []byte("/rand]")) {
outbytes = append(outbytes, bbcodeMissingTag...)
return i, start, lastTag, outbytes
}
break
} else if (len(msgbytes) - 1) < (i + 10) {
outbytes = append(outbytes, bbcodeMissingTag...)
return i, start, lastTag, outbytes
}
}
number, err := strconv.ParseInt(string(msgbytes[start:i]), 10, 64)
if err != nil {
outbytes = append(outbytes, bbcodeInvalidNumber...)
return i, start, lastTag, outbytes
}
// TODO: Add support for negative numbers?
if number < 0 {
outbytes = append(outbytes, bbcodeNoNegative...)
return i, start, lastTag, outbytes
}
var dat []byte
if number == 0 {
dat = []byte("0")
} else {
dat = []byte(strconv.FormatInt((bbcodeRandom.Int63n(number)), 10))
}
outbytes = append(outbytes, dat...)
i += 7
lastTag = i
return i, start, lastTag, outbytes
}

View File

@ -1,26 +0,0 @@
package extend
import c "git.tuxpa.in/a/gosora/common"
func init() {
c.Plugins.Add(&c.Plugin{UName: "heythere", Name: "Hey There", Author: "Azareal", URL: "https://github.com/Azareal", Init: initHeythere, Deactivate: deactivateHeythere})
}
// initHeythere is separate from init() as we don't want the plugin to run if the plugin is disabled
func initHeythere(plugin *c.Plugin) error {
plugin.AddHook("topic_reply_row_assign", heythereReply)
return nil
}
func deactivateHeythere(plugin *c.Plugin) {
plugin.RemoveHook("topic_reply_row_assign", heythereReply)
}
func heythereReply(data ...interface{}) interface{} {
currentUser := data[0].(*c.TopicPage).Header.CurrentUser
reply := data[1].(*c.ReplyUser)
reply.Content = "Hey there, " + currentUser.Name + "!"
reply.ContentHtml = "Hey there, " + currentUser.Name + "!"
reply.Tag = "Auto"
return nil
}

View File

@ -1,261 +0,0 @@
// Highly experimental plugin for caching rendered pages for guests
package extend
import (
//"log"
"bytes"
"errors"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"sync/atomic"
"time"
c "git.tuxpa.in/a/gosora/common"
"git.tuxpa.in/a/gosora/routes"
)
var hyperspace *Hyperspace
func init() {
c.Plugins.Add(&c.Plugin{UName: "hyperdrive", Name: "Hyperdrive", Author: "Azareal", Init: initHdrive, Deactivate: deactivateHdrive})
}
func initHdrive(pl *c.Plugin) error {
hyperspace = newHyperspace()
pl.AddHook("tasks_tick_topic_list", tickHdrive)
pl.AddHook("tasks_tick_widget_wol", tickHdriveWol)
pl.AddHook("route_topic_list_start", jumpHdriveTopicList)
pl.AddHook("route_forum_list_start", jumpHdriveForumList)
tickHdrive()
return nil
}
func deactivateHdrive(pl *c.Plugin) {
pl.RemoveHook("tasks_tick_topic_list", tickHdrive)
pl.RemoveHook("tasks_tick_widget_wol", tickHdriveWol)
pl.RemoveHook("route_topic_list_start", jumpHdriveTopicList)
pl.RemoveHook("route_forum_list_start", jumpHdriveForumList)
hyperspace = nil
}
type Hyperspace struct {
topicList atomic.Value
forumList atomic.Value
lastTopicListUpdate atomic.Value
}
func newHyperspace() *Hyperspace {
pageCache := new(Hyperspace)
blank := make(map[string][3][]byte, len(c.Themes))
pageCache.topicList.Store(blank)
pageCache.forumList.Store(blank)
pageCache.lastTopicListUpdate.Store(int64(0))
return pageCache
}
func tickHdriveWol(args ...interface{}) (skip bool, rerr c.RouteError) {
c.DebugLog("docking at wol")
return tickHdrive(args)
}
// TODO: Find a better way of doing this
func tickHdrive(args ...interface{}) (skip bool, rerr c.RouteError) {
c.DebugLog("Refueling...")
// Avoid accidentally caching already cached content
blank := make(map[string][3][]byte, len(c.Themes))
hyperspace.topicList.Store(blank)
hyperspace.forumList.Store(blank)
tListMap := make(map[string][3][]byte)
fListMap := make(map[string][3][]byte)
cacheTheme := func(tname string) (skip, fail bool, rerr c.RouteError) {
themeCookie := http.Cookie{Name: "current_theme", Value: tname, Path: "/", MaxAge: c.Year}
w := httptest.NewRecorder()
req := httptest.NewRequest("get", "/topics/", bytes.NewReader(nil))
req.AddCookie(&themeCookie)
user := c.GuestUser
head, rerr := c.UserCheck(w, req, &user)
if rerr != nil {
return true, true, rerr
}
rerr = routes.TopicList(w, req, &user, head)
if rerr != nil {
return true, true, rerr
}
if w.Code != 200 {
c.LogWarning(errors.New("not 200 for topic list in hyperdrive"))
return false, true, nil
}
buf := new(bytes.Buffer)
buf.ReadFrom(w.Result().Body)
gbuf, err := c.CompressBytesGzip(buf.Bytes())
if err != nil {
c.LogWarning(err)
return false, true, nil
}
bbuf, err := c.CompressBytesBrotli(buf.Bytes())
if err != nil {
c.LogWarning(err)
return false, true, nil
}
tListMap[tname] = [3][]byte{buf.Bytes(), gbuf, bbuf}
w = httptest.NewRecorder()
req = httptest.NewRequest("get", "/forums/", bytes.NewReader(nil))
user = c.GuestUser
head, rerr = c.UserCheck(w, req, &user)
if rerr != nil {
return true, true, rerr
}
rerr = routes.ForumList(w, req, &user, head)
if rerr != nil {
return true, true, rerr
}
if w.Code != 200 {
c.LogWarning(errors.New("not 200 for forum list in hyperdrive"))
return false, true, nil
}
buf = new(bytes.Buffer)
buf.ReadFrom(w.Result().Body)
gbuf, err = c.CompressBytesGzip(buf.Bytes())
if err != nil {
c.LogWarning(err)
return false, true, nil
}
bbuf, err = c.CompressBytesBrotli(buf.Bytes())
if err != nil {
c.LogWarning(err)
return false, true, nil
}
fListMap[tname] = [3][]byte{buf.Bytes(), gbuf, bbuf}
return false, false, nil
}
for tname, _ := range c.Themes {
skip, fail, rerr := cacheTheme(tname)
if fail || rerr != nil {
return skip, rerr
}
}
hyperspace.topicList.Store(tListMap)
hyperspace.forumList.Store(fListMap)
hyperspace.lastTopicListUpdate.Store(time.Now().Unix())
return false, nil
}
func jumpHdriveTopicList(args ...interface{}) (skip bool, rerr c.RouteError) {
theme := c.GetThemeByReq(args[1].(*http.Request))
p := hyperspace.topicList.Load().(map[string][3][]byte)
return jumpHdrive(p[theme.Name], args)
}
func jumpHdriveForumList(args ...interface{}) (skip bool, rerr c.RouteError) {
theme := c.GetThemeByReq(args[1].(*http.Request))
p := hyperspace.forumList.Load().(map[string][3][]byte)
return jumpHdrive(p[theme.Name], args)
}
func jumpHdrive( /*pg, */ p [3][]byte, args []interface{}) (skip bool, rerr c.RouteError) {
var tList []byte
w := args[0].(http.ResponseWriter)
r := args[1].(*http.Request)
var iw http.ResponseWriter
gzw, ok := w.(c.GzipResponseWriter)
//bzw, ok2 := w.(c.BrResponseWriter)
// !temp until global brotli
br := strings.Contains(r.Header.Get("Accept-Encoding"), "br")
if br && ok {
tList = p[2]
iw = gzw.ResponseWriter
} else if br {
tList = p[2]
iw = w
} else if ok {
tList = p[1]
iw = gzw.ResponseWriter
/*} else if ok2 {
tList = p[2]
iw = bzw.ResponseWriter
*/
} else {
tList = p[0]
iw = w
}
if len(tList) == 0 {
c.DebugLog("no itemlist in hyperspace")
return false, nil
}
//c.DebugLog("tList: ", tList)
// Avoid intercepting user requests as we only have guests in cache right now
user := args[2].(*c.User)
if user.ID != 0 {
c.DebugLog("not guest")
return false, nil
}
// Avoid intercepting search requests and filters as we don't have those in cache
//c.DebugLog("r.URL.Path:",r.URL.Path)
//c.DebugLog("r.URL.RawQuery:",r.URL.RawQuery)
if r.URL.RawQuery != "" {
return false, nil
}
if r.FormValue("js") == "1" || r.FormValue("i") == "1" {
return false, nil
}
c.DebugLog("Successful jump")
var etag string
lastUpdate := hyperspace.lastTopicListUpdate.Load().(int64)
c.DebugLog("lastUpdate:", lastUpdate)
if br {
h := iw.Header()
h.Set("X-I", "1")
h.Set("Content-Encoding", "br")
etag = "\"" + strconv.FormatInt(lastUpdate, 10) + "-b\""
} else if ok {
iw.Header().Set("X-I", "1")
etag = "\"" + strconv.FormatInt(lastUpdate, 10) + "-g\""
/*} else if ok2 {
iw.Header().Set("X-I", "1")
etag = "\"" + strconv.FormatInt(lastUpdate, 10) + "-b\""
*/
} else {
etag = "\"" + strconv.FormatInt(lastUpdate, 10) + "\""
}
if lastUpdate != 0 {
iw.Header().Set("ETag", etag)
if match := r.Header.Get("If-None-Match"); match != "" {
if strings.Contains(match, etag) {
iw.WriteHeader(http.StatusNotModified)
return true, nil
}
}
}
header := args[3].(*c.Header)
if br || ok /*ok2*/ {
iw.Header().Set("Content-Type", "text/html;charset=utf-8")
}
routes.FootHeaders(w, header)
iw.Write(tList)
return true, nil
}

View File

@ -1,398 +0,0 @@
package extend
import (
"strings"
c "git.tuxpa.in/a/gosora/common"
)
var markdownMaxDepth = 25 // How deep the parser will go when parsing Markdown strings
var markdownUnclosedElement []byte
var markdownBoldTagOpen []byte
var markdownBoldTagClose []byte
var markdownItalicTagOpen []byte
var markdownItalicTagClose []byte
var markdownUnderlineTagOpen []byte
var markdownUnderlineTagClose []byte
var markdownStrikeTagOpen []byte
var markdownStrikeTagClose []byte
var markdownQuoteTagOpen []byte
var markdownQuoteTagClose []byte
var markdownSpoilerTagOpen []byte
var markdownSpoilerTagClose []byte
var markdownH1TagOpen []byte
var markdownH1TagClose []byte
func init() {
c.Plugins.Add(&c.Plugin{UName: "markdown", Name: "Markdown", Author: "Azareal", URL: "https://github.com/Azareal", Init: InitMarkdown, Deactivate: deactivateMarkdown})
}
func InitMarkdown(pl *c.Plugin) error {
markdownUnclosedElement = []byte("<red>[Unclosed Element]</red>")
markdownBoldTagOpen = []byte("<b>")
markdownBoldTagClose = []byte("</b>")
markdownItalicTagOpen = []byte("<i>")
markdownItalicTagClose = []byte("</i>")
markdownUnderlineTagOpen = []byte("<u>")
markdownUnderlineTagClose = []byte("</u>")
markdownStrikeTagOpen = []byte("<s>")
markdownStrikeTagClose = []byte("</s>")
markdownQuoteTagOpen = []byte("<blockquote>")
markdownQuoteTagClose = []byte("</blockquote>")
markdownSpoilerTagOpen = []byte("<spoiler>")
markdownSpoilerTagClose = []byte("</spoiler>")
markdownH1TagOpen = []byte("<h2>")
markdownH1TagClose = []byte("</h2>")
pl.AddHook("parse_assign", MarkdownParse)
return nil
}
func deactivateMarkdown(pl *c.Plugin) {
pl.RemoveHook("parse_assign", MarkdownParse)
}
// An adapter for the parser, so that the parser can call itself recursively.
// This is less for the simple Markdown elements like bold and italics and more for the really complicated ones I plan on adding at some point.
func MarkdownParse(msg string) string {
msg = _markdownParse(msg+" ", 0)
if msg[len(msg)-1] == ' ' {
msg = msg[:len(msg)-1]
}
return msg
}
// Under Construction!
func _markdownParse(msg string, n int) string {
if n > markdownMaxDepth {
return "<red>[Markdown Error: Overflowed the max depth of 20]</red>"
}
var outbytes []byte
var lastElement int
breaking := false
//c.DebugLogf("Initial Msg: %+v\n", strings.Replace(msg, "\r", "\\r", -1))
for index := 0; index < len(msg); index++ {
simpleMatch := func(char byte, o []byte, c []byte) {
startIndex := index
if (index + 1) >= len(msg) {
breaking = true
return
}
index++
index = markdownSkipUntilChar(msg, index, char)
if (index-(startIndex+1)) < 1 || index >= len(msg) {
breaking = true
return
}
sIndex := startIndex + 1
lIndex := index
index++
outbytes = append(outbytes, msg[lastElement:startIndex]...)
outbytes = append(outbytes, o...)
// TODO: Implement this without as many type conversions
outbytes = append(outbytes, []byte(_markdownParse(msg[sIndex:lIndex], n+1))...)
outbytes = append(outbytes, c...)
lastElement = index
index--
}
startLine := func() {
startIndex := index
if (index + 1) >= len(msg) /*|| (index + 2) >= len(msg)*/ {
breaking = true
return
}
index++
index = markdownSkipUntilNotChar(msg, index, 32)
if (index + 1) >= len(msg) {
breaking = true
return
}
//index++
index = markdownSkipUntilStrongSpace(msg, index)
sIndex := startIndex + 1
lIndex := index
index++
outbytes = append(outbytes, msg[lastElement:startIndex]...)
outbytes = append(outbytes, markdownH1TagOpen...)
// TODO: Implement this without as many type conversions
//fmt.Println("msg[sIndex:lIndex]:", string(msg[sIndex:lIndex]))
// TODO: Quick hack to eliminate trailing spaces...
outbytes = append(outbytes, []byte(strings.TrimSpace(_markdownParse(msg[sIndex:lIndex], n+1)))...)
outbytes = append(outbytes, markdownH1TagClose...)
lastElement = index
index--
}
uniqueWord := func(i int) bool {
if i == 0 {
return true
}
return msg[i-1] <= 32
}
switch msg[index] {
// TODO: Do something slightly less hacky for skipping URLs
case '/':
if len(msg) > (index+2) && msg[index+1] == '/' {
for ; index < len(msg) && msg[index] != ' '; index++ {
}
index--
continue
}
case '_':
if !uniqueWord(index) {
break
}
simpleMatch('_', markdownUnderlineTagOpen, markdownUnderlineTagClose)
if breaking {
break
}
case '~':
simpleMatch('~', markdownStrikeTagOpen, markdownStrikeTagClose)
if breaking {
break
}
case '*':
startIndex := index
italic := true
bold := false
if (index + 2) < len(msg) {
if msg[index+1] == '*' {
bold = true
index++
if msg[index+1] != '*' {
italic = false
} else {
index++
}
}
}
// Does the string terminate abruptly?
if (index + 1) >= len(msg) {
break
}
index++
index = markdownSkipUntilAsterisk(msg, index)
if index >= len(msg) {
break
}
preBreak := func() {
outbytes = append(outbytes, msg[lastElement:startIndex]...)
lastElement = startIndex
}
sIndex := startIndex
lIndex := index
if bold && italic {
if (index + 3) >= len(msg) {
preBreak()
break
}
index += 3
sIndex += 3
} else if bold {
if (index + 2) >= len(msg) {
preBreak()
break
}
index += 2
sIndex += 2
} else {
if (index + 1) >= len(msg) {
preBreak()
break
}
index++
sIndex++
}
if lIndex <= sIndex {
preBreak()
break
}
if sIndex < 0 || lIndex < 0 {
preBreak()
break
}
outbytes = append(outbytes, msg[lastElement:startIndex]...)
if bold {
outbytes = append(outbytes, markdownBoldTagOpen...)
}
if italic {
outbytes = append(outbytes, markdownItalicTagOpen...)
}
// TODO: Implement this without as many type conversions
outbytes = append(outbytes, []byte(_markdownParse(msg[sIndex:lIndex], n+1))...)
if italic {
outbytes = append(outbytes, markdownItalicTagClose...)
}
if bold {
outbytes = append(outbytes, markdownBoldTagClose...)
}
lastElement = index
index--
case '\\':
if (index + 1) < len(msg) {
if isMarkdownStartChar(msg[index+1]) && msg[index+1] != '\\' {
outbytes = append(outbytes, msg[lastElement:index]...)
index++
lastElement = index
}
}
// TODO: Add a inline quote variant
case '`':
simpleMatch('`', markdownQuoteTagOpen, markdownQuoteTagClose)
if breaking {
break
}
// TODO: Might need to be double pipe
case '|':
simpleMatch('|', markdownSpoilerTagOpen, markdownSpoilerTagClose)
if breaking {
break
}
case 10: // newline
if (index + 1) >= len(msg) {
break
}
index++
if msg[index] != '#' {
continue
}
startLine()
if breaking {
break
}
case '#':
if index != 0 {
continue
}
startLine()
if breaking {
break
}
}
}
if len(outbytes) == 0 {
return msg
} else if lastElement < (len(msg) - 1) {
msg = string(outbytes) + msg[lastElement:]
return msg
}
return string(outbytes)
}
func isMarkdownStartChar(ch byte) bool { // char
return ch == '\\' || ch == '~' || ch == '_' || ch == 10 || ch == '`' || ch == '*' || ch == '|'
}
func markdownFindChar(data string, index int, char byte) bool {
for ; index < len(data); index++ {
item := data[index]
if item > 32 {
return (item == char)
}
}
return false
}
func markdownSkipUntilChar(data string, index int, char byte) int {
for ; index < len(data); index++ {
if data[index] == char {
break
}
}
return index
}
func markdownSkipUntilNotChar(data string, index int, char byte) int {
for ; index < len(data); index++ {
if data[index] != char {
break
}
}
return index
}
func markdownSkipUntilStrongSpace(data string, index int) int {
inSpace := false
for ; index < len(data); index++ {
if inSpace && data[index] == 32 {
index--
break
} else if data[index] == 32 {
inSpace = true
} else if data[index] < 32 {
break
} else {
inSpace = false
}
}
return index
}
func markdownSkipUntilAsterisk(data string, index int) int {
SwitchLoop:
for ; index < len(data); index++ {
switch data[index] {
case 10:
if ((index + 1) < len(data)) && markdownFindChar(data, index, '*') {
index = markdownSkipList(data, index)
}
case '*':
break SwitchLoop
}
}
return index
}
// plugin_markdown doesn't support lists yet, but I want it to be easy to have nested lists when we do have them
func markdownSkipList(data string, index int) int {
var lastNewline int
datalen := len(data)
for ; index < datalen; index++ {
SkipListInnerLoop:
if data[index] == 10 {
lastNewline = index
for ; index < datalen; index++ {
if data[index] > 32 {
break
} else if data[index] == 10 {
goto SkipListInnerLoop
}
}
if index >= datalen {
if data[index] != '*' && data[index] != '-' {
if (lastNewline + 1) < datalen {
return lastNewline + 1
}
return lastNewline
}
}
}
}
return index
}

View File

@ -1,39 +0,0 @@
package extend
import c "git.tuxpa.in/a/gosora/common"
func init() {
/*
The UName field should match the name in the URL minus plugin_ and the file extension. The same name as the map index. Please choose a unique name which won't clash with any other plugins.
The Name field is for the friendly name of the plugin shown to the end-user.
The Author field is the author of this plugin. The one who created it.
The URL field is for the URL pointing to the location where you can download this plugin.
The Settings field points to the route for managing the settings for this plugin. Coming soon.
The Tag field is for providing a tiny snippet of information separate from the description.
The Type field is for the type of the plugin. This gets changed to "go" automatically and we would suggest leaving "".
The Init field is for the initialisation handler which is called by the software to run this plugin. This expects a function. You should add your hooks, init logic, initial queries, etc. in said function.
The Activate field is for the handler which is called by the software when the admin hits the Activate button in the control panel. This is separate from the Init handler which is called upon the start of the server and upon activation. Use nil if you don't have a handler for this.
The Deactivate field is for the handler which is called by the software when the admin hits the Deactivate button in the control panel. You should clean-up any resources you have allocated, remove any hooks, close any statements, etc. within this handler.
The Installation field is for one-off installation logic such as creating tables. You will need to run the separate uninstallation function for that.
That Uninstallation field which is currently unused is for not only deactivating this plugin, but for purging any data associated with it such a new tables or data produced by the end-user.
*/
c.Plugins.Add(&c.Plugin{UName: "skeleton", Name: "Skeleton", Author: "Azareal", Init: initSkeleton, Activate: activateSkeleton, Deactivate: deactivateSkeleton})
}
func initSkeleton(pl *c.Plugin) error { return nil }
// Any errors encountered while trying to activate the plugin are reported back to the admin and the activation is aborted
func activateSkeleton(pl *c.Plugin) error { return nil }
func deactivateSkeleton(pl *c.Plugin) {}

2
go.mod
View File

@ -13,8 +13,6 @@ require (
github.com/lib/pq v1.0.0
github.com/mailru/easyjson v0.7.0 // indirect
github.com/olivere/elastic v6.2.16+incompatible // indirect
github.com/oschwald/geoip2-golang v1.2.1
github.com/oschwald/maxminddb-golang v1.3.0 // indirect
github.com/pkg/errors v0.9.1
github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79

View File

@ -1,9 +0,0 @@
@echo off
echo Building the router generator
go build -ldflags="-s -w"
if %errorlevel% neq 0 (
pause
exit /b %errorlevel%
)
echo The router generator was successfully built
pause

View File

@ -1,10 +0,0 @@
@echo off
echo Building the router generator
go build -ldflags="-s -w"
if %errorlevel% neq 0 (
pause
exit /b %errorlevel%
)
echo The router generator was successfully built
router_gen.exe
pause