more cleanup
This commit is contained in:
parent
77d85278a2
commit
9970be19e5
|
@ -1 +0,0 @@
|
||||||
This file is here so that Git will include this folder in the repository.
|
|
|
@ -1 +0,0 @@
|
||||||
This file is here so that Git will include this folder in the repository.
|
|
|
@ -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
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
/* Copyright Azareal 2016 - 2017 */
|
|
||||||
package main
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
/* Copyright Azareal 2016 - 2017 */
|
|
||||||
package main
|
|
||||||
|
|
|
@ -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;"> </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>
|
|
|
@ -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
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -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>
|
|
|
@ -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"
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
package adventure
|
|
||||||
|
|
||||||
type AdventureStore interface {
|
|
||||||
Create() (int, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type DefaultAdventureStore struct {
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"UName":"adventure",
|
|
||||||
"Name":"Adventure",
|
|
||||||
"Author":"Azareal",
|
|
||||||
"URL":"https://git.tuxpa.in/a/gosora",
|
|
||||||
"Skip":true
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
This file is here so that Git will include this folder in the repository.
|
|
|
@ -1 +0,0 @@
|
||||||
package extend
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"UName":"guilds",
|
|
||||||
"Name":"Guilds",
|
|
||||||
"Author":"Azareal",
|
|
||||||
"URL":"https://git.tuxpa.in/a/gosora",
|
|
||||||
"Skip":true
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
This file is here so that Git will include this folder in the repository.
|
|
|
@ -1,5 +0,0 @@
|
||||||
current_page.test = true;
|
|
||||||
|
|
||||||
// This shouldn't ever fail
|
|
||||||
var errmsg = "gotcha";
|
|
||||||
errmsg;
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"UName":"heytherejs",
|
|
||||||
"Name":"HeythereJS",
|
|
||||||
"Author":"Azareal",
|
|
||||||
"URL":"https://git.tuxpa.in/a/gosora",
|
|
||||||
"Main":"main.js"
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
2
go.mod
|
@ -13,8 +13,6 @@ require (
|
||||||
github.com/lib/pq v1.0.0
|
github.com/lib/pq v1.0.0
|
||||||
github.com/mailru/easyjson v0.7.0 // indirect
|
github.com/mailru/easyjson v0.7.0 // indirect
|
||||||
github.com/olivere/elastic v6.2.16+incompatible // 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/pkg/errors v0.9.1
|
||||||
github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d
|
github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d
|
||||||
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79
|
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
|
Loading…
Reference in New Issue