aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Dockerfile12
-rw-r--r--README.md13
-rw-r--r--accept.go2
-rw-r--r--activityPubStruct.go236
-rw-r--r--cacheDatabase.go124
-rw-r--r--client.go327
-rw-r--r--config-init5
-rw-r--r--config-init.docker42
-rw-r--r--database.go172
-rw-r--r--docker-compose.yml26
-rw-r--r--follow.go13
-rw-r--r--main.go536
-rw-r--r--outboxGet.go4
-rw-r--r--outboxPost.go29
-rw-r--r--session.go21
-rw-r--r--static/anews.html24
-rw-r--r--static/archive.html2
-rw-r--r--static/bottom.html23
-rw-r--r--static/css/themes/default.css197
-rw-r--r--static/css/themes/gruvbox.css175
-rw-r--r--static/index.html52
-rw-r--r--static/js/posts.js29
-rw-r--r--static/js/themes.js40
-rw-r--r--static/main.html93
-rw-r--r--static/manage.html6
-rw-r--r--static/nadmin.html8
-rw-r--r--static/ncatalog.html63
-rw-r--r--static/news.html8
-rw-r--r--static/nposts.html30
-rw-r--r--static/posts.html12
-rw-r--r--static/top.html132
-rw-r--r--tripcode.go51
-rw-r--r--verification.go252
-rw-r--r--webfinger.go8
34 files changed, 1662 insertions, 1105 deletions
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..050cd0b
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,12 @@
+FROM golang:1.16-alpine AS builder
+WORKDIR /build
+COPY . .
+RUN go build .
+
+FROM alpine:3.14
+RUN apk --no-cache add imagemagick exiv2 ttf-opensans
+WORKDIR /app
+COPY --from=builder /build/Server /app
+COPY static/ /app/static/
+COPY databaseschema.psql /app
+CMD ["/app/Server"]
diff --git a/README.md b/README.md
index a05eed7..4d4bba2 100644
--- a/README.md
+++ b/README.md
@@ -73,6 +73,8 @@ Any contributions or suggestions are appreciated. Best way to give immediate fee
`instancesalt:put your salt string here` Used for secure tripcodes currently.
+ `redis:redis://localhost` Used for Redis. This should be `redis://localhost` in most cases.
+
Currently e-mail is not implemented to do anything special, but the code is in place
@@ -151,7 +153,16 @@ server {
### Docker
-`Please consider submitting a pull request if you set up a FChannel instance with Docker with instructions on how to do so`
+A Dockerfile is provided, and an example `docker-compose.yml` exists to base your Docker setup on.
+You should use the `config-init.docker` file to configure it and it will work more or less out of the box with it, you should just need some minor configuration changes to test it out.
+
+You may need to set up your `config` like this to access the mod tools if you get stuck when logging in:
+```
+instance:fchan.xyz:PORT GOES HERE
+instanceport:PORT GOES HERE
+```
+
+See #12 for more details.
# Support
diff --git a/accept.go b/accept.go
index 1263ab8..b357cc4 100644
--- a/accept.go
+++ b/accept.go
@@ -11,7 +11,7 @@ func acceptActivity(header string) bool {
if strings.Contains(header, ";") {
split := strings.Split(header, ";")
accept = accept || activityRegexp.MatchString(split[0])
- accept = accept || strings.Contains(split[len(split)-1], "profile=\"https://www.w3.org/ns/activitystreams\"")
+ accept = accept || strings.Contains(split[len(split)-1], "profile=\"https://www.w3.org/ns/activitystreams\"")
} else {
accept = accept || activityRegexp.MatchString(header)
}
diff --git a/activityPubStruct.go b/activityPubStruct.go
index 62fabe1..8676b12 100644
--- a/activityPubStruct.go
+++ b/activityPubStruct.go
@@ -1,6 +1,8 @@
package main
import (
+ "time"
+
"encoding/json"
"html/template"
)
@@ -11,17 +13,17 @@ type AtContextRaw struct {
type ActivityRaw struct {
AtContextRaw
- Type string `json:"type,omitempty"`
- Id string `json:"id,omitempty"`
- Name string `json:"name,omitempty"`
- Summary string `json:"summary,omitempty"`
- Auth string `json:"auth,omitempty"`
- ToRaw json.RawMessage `json:"to,omitempty"`
- BtoRaw json.RawMessage `json:"bto,omitempty"`
- CcRaw json.RawMessage `json:"cc,omitempty"`
- Published string `json:"published,omitempty"`
- ActorRaw json.RawMessage `json:"actor,omitempty"`
- ObjectRaw json.RawMessage `json:"object,omitempty"`
+ Type string `json:"type,omitempty"`
+ Id string `json:"id,omitempty"`
+ Name string `json:"name,omitempty"`
+ Summary string `json:"summary,omitempty"`
+ Auth string `json:"auth,omitempty"`
+ ToRaw json.RawMessage `json:"to,omitempty"`
+ BtoRaw json.RawMessage `json:"bto,omitempty"`
+ CcRaw json.RawMessage `json:"cc,omitempty"`
+ Published time.Time `json:"published,omitempty"`
+ ActorRaw json.RawMessage `json:"actor,omitempty"`
+ ObjectRaw json.RawMessage `json:"object,omitempty"`
}
type AtContext struct {
@@ -29,7 +31,7 @@ type AtContext struct {
}
type AtContextArray struct {
- Context []interface {} `json:"@context,omitempty"`
+ Context []interface{} `json:"@context,omitempty"`
}
type AtContextString struct {
@@ -69,133 +71,133 @@ type CcOjectString struct {
}
type Actor struct {
- Type string `json:"type,omitempty"`
- Id string `json:"id,omitempty"`
- Inbox string `json:"inbox,omitempty"`
- Outbox string `json:"outbox,omitempty"`
- Following string `json:"following,omitempty"`
- Followers string `json:"followers,omitempty"`
- Name string `json:"name,omitempty"`
- PreferredUsername string `json:"preferredUsername,omitempty"`
- PublicKey PublicKeyPem `json:"publicKey,omitempty"`
- Summary string `json:"summary,omitempty"`
- AuthRequirement []string `json:"authrequirement,omitempty"`
- Restricted bool `json:"restricted"`
+ Type string `json:"type,omitempty"`
+ Id string `json:"id,omitempty"`
+ Inbox string `json:"inbox,omitempty"`
+ Outbox string `json:"outbox,omitempty"`
+ Following string `json:"following,omitempty"`
+ Followers string `json:"followers,omitempty"`
+ Name string `json:"name,omitempty"`
+ PreferredUsername string `json:"preferredUsername,omitempty"`
+ PublicKey PublicKeyPem `json:"publicKey,omitempty"`
+ Summary string `json:"summary,omitempty"`
+ AuthRequirement []string `json:"authrequirement,omitempty"`
+ Restricted bool `json:"restricted"`
}
type PublicKeyPem struct {
- Id string `json:"id,omitempty"`
- Owner string `json:"owner,omitempty"`
- PublicKeyPem string `json:"publicKeyPem,omitempty"`
+ Id string `json:"id,omitempty"`
+ Owner string `json:"owner,omitempty"`
+ PublicKeyPem string `json:"publicKeyPem,omitempty"`
}
type Activity struct {
AtContext
- Type string `json:"type,omitempty"`
- Id string `json:"id,omitempty"`
- Actor *Actor `json:"actor,omitempty"`
- Name string `json:"name,omitempty"`
- Summary string `json:"summary,omitempty"`
- Auth string `json:"auth,omitempty"`
- To []string `json:"to, omitempty"`
- Bto []string `json:"bto,omitempty"`
- Cc []string `json:"cc, omitempty"`
- Published string `json:"published,omitempty"`
- Object *ObjectBase `json:"object, omitempty"`
+ Type string `json:"type,omitempty"`
+ Id string `json:"id,omitempty"`
+ Actor *Actor `json:"actor,omitempty"`
+ Name string `json:"name,omitempty"`
+ Summary string `json:"summary,omitempty"`
+ Auth string `json:"auth,omitempty"`
+ To []string `json:"to,omitempty"`
+ Bto []string `json:"bto,omitempty"`
+ Cc []string `json:"cc,omitempty"`
+ Published time.Time `json:"published,omitempty"`
+ Object *ObjectBase `json:"object,omitempty"`
}
type ObjectBase struct {
- Type string `json:"type,omitempty"`
- Id string `json:"id,omitempty"`
- Name string `json:"name,omitempty"`
- Option []string `json:"option,omitempty"`
- Alias string `json:"alias,omitempty"`
- AttributedTo string `json:"attributedTo,omitempty"`
- TripCode string `json:"tripcode,omitempty"`
- Actor string `json:"actor,omitempty"`
- Audience string `json:"audience,omitempty"`
- ContentHTML template.HTML `json:"contenthtml,omitempty"`
- Content string `json:"content,omitempty"`
- EndTime string `json:"endTime,omitempty"`
- Generator string `json:"generator,omitempty"`
- Icon string `json:"icon,omitempty"`
- Image string `json:"image,omitempty"`
- InReplyTo []ObjectBase `json:"inReplyTo,omitempty"`
- Location string `json:"location,omitempty"`
- Preview *NestedObjectBase `json:"preview,omitempty"`
- Published string `json:"published,omitempty"`
- Updated string `json:"updated,omitempty"`
- Object *NestedObjectBase `json:"object,omitempty"`
- Attachment []ObjectBase `json:"attachment,omitempty"`
- Replies *CollectionBase `json:"replies,omitempty"`
- StartTime string `json:"startTime,omitempty"`
- Summary string `json:"summary,omitempty"`
- Tag []ObjectBase `json:"tag,omitempty"`
- Wallet []CryptoCur `json:"wallet,omitempty"`
- Deleted string `json:"deleted,omitempty"`
- Url []ObjectBase `json:"url,omitempty"`
- Href string `json:"href,omitempty"`
- To []string `json:"to,omitempty"`
- Bto []string `json:"bto,omitempty"`
- Cc []string `json:"cc,omitempty"`
- Bcc string `json:"Bcc,omitempty"`
- MediaType string `json:"mediatype,omitempty"`
- Duration string `json:"duration,omitempty"`
- Size int64 `json:"size,omitempty"`
- Sensitive bool `json:"sensitive,omitempty"`
+ Type string `json:"type,omitempty"`
+ Id string `json:"id,omitempty"`
+ Name string `json:"name,omitempty"`
+ Option []string `json:"option,omitempty"`
+ Alias string `json:"alias,omitempty"`
+ AttributedTo string `json:"attributedTo,omitempty"`
+ TripCode string `json:"tripcode,omitempty"`
+ Actor string `json:"actor,omitempty"`
+ Audience string `json:"audience,omitempty"`
+ ContentHTML template.HTML `json:"contenthtml,omitempty"`
+ Content string `json:"content,omitempty"`
+ EndTime string `json:"endTime,omitempty"`
+ Generator string `json:"generator,omitempty"`
+ Icon string `json:"icon,omitempty"`
+ Image string `json:"image,omitempty"`
+ InReplyTo []ObjectBase `json:"inReplyTo,omitempty"`
+ Location string `json:"location,omitempty"`
+ Preview *NestedObjectBase `json:"preview,omitempty"`
+ Published time.Time `json:"published,omitempty"`
+ Updated time.Time `json:"updated,omitempty"`
+ Object *NestedObjectBase `json:"object,omitempty"`
+ Attachment []ObjectBase `json:"attachment,omitempty"`
+ Replies *CollectionBase `json:"replies,omitempty"`
+ StartTime string `json:"startTime,omitempty"`
+ Summary string `json:"summary,omitempty"`
+ Tag []ObjectBase `json:"tag,omitempty"`
+ Wallet []CryptoCur `json:"wallet,omitempty"`
+ Deleted string `json:"deleted,omitempty"`
+ Url []ObjectBase `json:"url,omitempty"`
+ Href string `json:"href,omitempty"`
+ To []string `json:"to,omitempty"`
+ Bto []string `json:"bto,omitempty"`
+ Cc []string `json:"cc,omitempty"`
+ Bcc string `json:"Bcc,omitempty"`
+ MediaType string `json:"mediatype,omitempty"`
+ Duration string `json:"duration,omitempty"`
+ Size int64 `json:"size,omitempty"`
+ Sensitive bool `json:"sensitive,omitempty"`
}
type CryptoCur struct {
- Type string `json:"type,omitempty"`
+ Type string `json:"type,omitempty"`
Address string `json:"address,omitempty"`
}
type NestedObjectBase struct {
AtContext
- Type string `json:"type,omitempty"`
- Id string `json:"id,omitempty"`
- Name string `json:"name,omitempty"`
- Alias string `json:"alias,omitempty"`
- AttributedTo string `json:"attributedTo,omitempty"`
- TripCode string `json:"tripcode,omitempty"`
- Actor string `json:"actor,omitempty"`
- Audience string `json:"audience,omitempty"`
- ContentHTML template.HTML `json:"contenthtml,omitempty"`
- Content string `json:"content,omitempty"`
- EndTime string `json:"endTime,omitempty"`
- Generator string `json:"generator,omitempty"`
- Icon string `json:"icon,omitempty"`
- Image string `json:"image,omitempty"`
- InReplyTo []ObjectBase `json:"inReplyTo,omitempty"`
- Location string `json:"location,omitempty"`
- Preview ObjectBase `json:"preview,omitempty"`
- Published string `json:"published,omitempty"`
- Attachment []ObjectBase `json:"attachment,omitempty"`
- Replies *CollectionBase `json:"replies,omitempty"`
- StartTime string `json:"startTime,omitempty"`
- Summary string `json:"summary,omitempty"`
- Tag []ObjectBase `json:"tag,omitempty"`
- Updated string `json:"updated,omitempty"`
- Deleted string `json:"deleted,omitempty"`
- Url []ObjectBase `json:"url,omitempty"`
- Href string `json:"href,omitempty"`
- To []string `json:"to,omitempty"`
- Bto []string `json:"bto,omitempty"`
- Cc []string `json:"cc,omitempty"`
- Bcc string `json:"Bcc,omitempty"`
- MediaType string `json:"mediatype,omitempty"`
- Duration string `json:"duration,omitempty"`
- Size int64 `json:"size,omitempty"`
+ Type string `json:"type,omitempty"`
+ Id string `json:"id,omitempty"`
+ Name string `json:"name,omitempty"`
+ Alias string `json:"alias,omitempty"`
+ AttributedTo string `json:"attributedTo,omitempty"`
+ TripCode string `json:"tripcode,omitempty"`
+ Actor string `json:"actor,omitempty"`
+ Audience string `json:"audience,omitempty"`
+ ContentHTML template.HTML `json:"contenthtml,omitempty"`
+ Content string `json:"content,omitempty"`
+ EndTime string `json:"endTime,omitempty"`
+ Generator string `json:"generator,omitempty"`
+ Icon string `json:"icon,omitempty"`
+ Image string `json:"image,omitempty"`
+ InReplyTo []ObjectBase `json:"inReplyTo,omitempty"`
+ Location string `json:"location,omitempty"`
+ Preview ObjectBase `json:"preview,omitempty"`
+ Published time.Time `json:"published,omitempty"`
+ Attachment []ObjectBase `json:"attachment,omitempty"`
+ Replies *CollectionBase `json:"replies,omitempty"`
+ StartTime string `json:"startTime,omitempty"`
+ Summary string `json:"summary,omitempty"`
+ Tag []ObjectBase `json:"tag,omitempty"`
+ Updated time.Time `json:"updated,omitempty"`
+ Deleted string `json:"deleted,omitempty"`
+ Url []ObjectBase `json:"url,omitempty"`
+ Href string `json:"href,omitempty"`
+ To []string `json:"to,omitempty"`
+ Bto []string `json:"bto,omitempty"`
+ Cc []string `json:"cc,omitempty"`
+ Bcc string `json:"Bcc,omitempty"`
+ MediaType string `json:"mediatype,omitempty"`
+ Duration string `json:"duration,omitempty"`
+ Size int64 `json:"size,omitempty"`
}
type CollectionBase struct {
- Actor *Actor `json:"actor,omitempty"`
- Summary string `json:"summary,omitempty"`
- Type string `json:"type,omitempty"`
- TotalItems int `json:"totalItems,omitempty"`
- TotalImgs int `json:"totalImgs,omitempty"`
+ Actor *Actor `json:"actor,omitempty"`
+ Summary string `json:"summary,omitempty"`
+ Type string `json:"type,omitempty"`
+ TotalItems int `json:"totalItems,omitempty"`
+ TotalImgs int `json:"totalImgs,omitempty"`
OrderedItems []ObjectBase `json:"orderedItems,omitempty"`
- Items []ObjectBase `json:"items,omitempty"`
+ Items []ObjectBase `json:"items,omitempty"`
}
type Collection struct {
diff --git a/cacheDatabase.go b/cacheDatabase.go
index 2fe0a38..d735cfe 100644
--- a/cacheDatabase.go
+++ b/cacheDatabase.go
@@ -6,16 +6,16 @@ import _ "github.com/lib/pq"
func WriteObjectToCache(db *sql.DB, obj ObjectBase) ObjectBase {
- if(IsPostBlacklist(db, obj.Content)){
+ if IsPostBlacklist(db, obj.Content) {
fmt.Println("\n\nBlacklist post blocked\n\n")
return obj
}
-
+
if len(obj.Attachment) > 0 {
if obj.Preview.Href != "" {
WritePreviewToCache(db, *obj.Preview)
}
-
+
for i, _ := range obj.Attachment {
WriteAttachmentToCache(db, obj.Attachment[i])
WriteActivitytoCacheWithAttachment(db, obj, obj.Attachment[i], *obj.Preview)
@@ -38,19 +38,19 @@ func WriteObjectToCache(db *sql.DB, obj ObjectBase) ObjectBase {
func WriteActorObjectToCache(db *sql.DB, obj ObjectBase) ObjectBase {
- if(IsPostBlacklist(db, obj.Content)){
+ if IsPostBlacklist(db, obj.Content) {
return obj
}
-
+
if len(obj.Attachment) > 0 {
-
+
if IsIDLocal(db, obj.Id) {
return obj
}
if obj.Preview.Href != "" {
WritePreviewToCache(db, *obj.Preview)
}
-
+
for i, _ := range obj.Attachment {
WriteAttachmentToCache(db, obj.Attachment[i])
WriteActivitytoCacheWithAttachment(db, obj, obj.Attachment[i], *obj.Preview)
@@ -83,7 +83,7 @@ func WriteActivitytoCache(db *sql.DB, obj ObjectBase) {
CheckError(err, "error selecting obj id from cache")
- var id string
+ var id string
defer rows.Close()
rows.Next()
rows.Scan(&id)
@@ -92,22 +92,22 @@ func WriteActivitytoCache(db *sql.DB, obj ObjectBase) {
return
}
- if obj.Updated == "" {
+ if obj.Updated.IsZero() {
obj.Updated = obj.Published
}
query = `insert into cacheactivitystream (id, type, name, content, published, updated, attributedto, actor, tripcode, sensitive) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`
- _, e := db.Exec(query, obj.Id ,obj.Type, obj.Name, obj.Content, obj.Published, obj.Updated, obj.AttributedTo, obj.Actor, obj.TripCode, obj.Sensitive)
-
- if e != nil{
+ _, e := db.Exec(query, obj.Id, obj.Type, obj.Name, obj.Content, obj.Published, obj.Updated, obj.AttributedTo, obj.Actor, obj.TripCode, obj.Sensitive)
+
+ if e != nil {
fmt.Println("error inserting new activity cache")
- panic(e)
- }
+ panic(e)
+ }
}
func WriteActivitytoCacheWithAttachment(db *sql.DB, obj ObjectBase, attachment ObjectBase, preview NestedObjectBase) {
-
+
obj.Name = EscapeString(obj.Name)
obj.Content = EscapeString(obj.Content)
obj.AttributedTo = EscapeString(obj.AttributedTo)
@@ -118,7 +118,7 @@ func WriteActivitytoCacheWithAttachment(db *sql.DB, obj ObjectBase, attachment O
CheckError(err, "error selecting activity with attachment obj id cache")
- var id string
+ var id string
defer rows.Close()
rows.Next()
rows.Scan(&id)
@@ -127,18 +127,18 @@ func WriteActivitytoCacheWithAttachment(db *sql.DB, obj ObjectBase, attachment O
return
}
- if obj.Updated == "" {
+ if obj.Updated.IsZero() {
obj.Updated = obj.Published
- }
+ }
query = `insert into cacheactivitystream (id, type, name, content, attachment, preview, published, updated, attributedto, actor, tripcode, sensitive) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`
- _, e := db.Exec(query, obj.Id ,obj.Type, obj.Name, obj.Content, attachment.Id, preview.Id, obj.Published, obj.Updated, obj.AttributedTo, obj.Actor, obj.TripCode, obj.Sensitive)
-
- if e != nil{
+ _, e := db.Exec(query, obj.Id, obj.Type, obj.Name, obj.Content, attachment.Id, preview.Id, obj.Published, obj.Updated, obj.AttributedTo, obj.Actor, obj.TripCode, obj.Sensitive)
+
+ if e != nil {
fmt.Println("error inserting new activity with attachment cache")
- panic(e)
- }
+ panic(e)
+ }
}
func WriteAttachmentToCache(db *sql.DB, obj ObjectBase) {
@@ -149,7 +149,7 @@ func WriteAttachmentToCache(db *sql.DB, obj ObjectBase) {
CheckError(err, "error selecting attachment obj id cache")
- var id string
+ var id string
defer rows.Close()
rows.Next()
rows.Scan(&id)
@@ -158,17 +158,17 @@ func WriteAttachmentToCache(db *sql.DB, obj ObjectBase) {
return
}
- if obj.Updated == "" {
+ if obj.Updated.IsZero() {
obj.Updated = obj.Published
- }
-
+ }
+
query = `insert into cacheactivitystream (id, type, name, href, published, updated, attributedTo, mediatype, size) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)`
-
- _, e := db.Exec(query, obj.Id ,obj.Type, obj.Name, obj.Href, obj.Published, obj.Updated, obj.AttributedTo, obj.MediaType, obj.Size)
-
- if e != nil{
+
+ _, e := db.Exec(query, obj.Id, obj.Type, obj.Name, obj.Href, obj.Published, obj.Updated, obj.AttributedTo, obj.MediaType, obj.Size)
+
+ if e != nil {
fmt.Println("error inserting new attachment cache")
- panic(e)
+ panic(e)
}
}
@@ -180,7 +180,7 @@ func WritePreviewToCache(db *sql.DB, obj NestedObjectBase) {
CheckError(err, "error selecting preview obj id cache")
- var id string
+ var id string
defer rows.Close()
rows.Next()
rows.Scan(&id)
@@ -189,24 +189,24 @@ func WritePreviewToCache(db *sql.DB, obj NestedObjectBase) {
return
}
- if obj.Updated == "" {
+ if obj.Updated.IsZero() {
obj.Updated = obj.Published
- }
-
+ }
+
query = `insert into cacheactivitystream (id, type, name, href, published, updated, attributedTo, mediatype, size) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)`
-
- _, e := db.Exec(query, obj.Id ,obj.Type, obj.Name, obj.Href, obj.Published, obj.Updated, obj.AttributedTo, obj.MediaType, obj.Size)
-
- if e != nil{
+
+ _, e := db.Exec(query, obj.Id, obj.Type, obj.Name, obj.Href, obj.Published, obj.Updated, obj.AttributedTo, obj.MediaType, obj.Size)
+
+ if e != nil {
fmt.Println("error inserting new preview cache")
- panic(e)
+ panic(e)
}
}
func WriteObjectReplyToCache(db *sql.DB, obj ObjectBase) {
-
+
for i, e := range obj.InReplyTo {
- if(i == 0 || IsReplyInThread(db, obj.InReplyTo[0].Id, e.Id)){
+ if i == 0 || IsReplyInThread(db, obj.InReplyTo[0].Id, e.Id) {
query := `select id from replies where id=$1`
@@ -214,7 +214,7 @@ func WriteObjectReplyToCache(db *sql.DB, obj ObjectBase) {
CheckError(err, "error selecting obj id cache reply")
- var id string
+ var id string
defer rows.Close()
rows.Next()
rows.Scan(&id)
@@ -222,14 +222,14 @@ func WriteObjectReplyToCache(db *sql.DB, obj ObjectBase) {
if id != "" {
return
}
-
+
query = `insert into cachereplies (id, inreplyto) values ($1, $2)`
- _, err = db.Exec(query, obj.Id, e.Id)
-
- if err != nil{
+ _, err = db.Exec(query, obj.Id, e.Id)
+
+ if err != nil {
fmt.Println("error inserting replies cache")
- panic(err)
+ panic(err)
}
}
}
@@ -237,13 +237,13 @@ func WriteObjectReplyToCache(db *sql.DB, obj ObjectBase) {
if len(obj.InReplyTo) < 1 {
query := `insert into cachereplies (id, inreplyto) values ($1, $2)`
- _, err := db.Exec(query, obj.Id, "")
-
- if err != nil{
+ _, err := db.Exec(query, obj.Id, "")
+
+ if err != nil {
fmt.Println("error inserting replies cache")
- panic(err)
+ panic(err)
}
- }
+ }
}
func WriteObjectReplyCache(db *sql.DB, obj ObjectBase) {
@@ -257,7 +257,7 @@ func WriteObjectReplyCache(db *sql.DB, obj ObjectBase) {
CheckError(err, "error selecting obj id cache reply")
- var inreplyto string
+ var inreplyto string
defer rows.Close()
rows.Next()
rows.Scan(&inreplyto)
@@ -265,14 +265,14 @@ func WriteObjectReplyCache(db *sql.DB, obj ObjectBase) {
if inreplyto != "" {
return
}
-
+
query = `insert into cachereplies (id, inreplyto) values ($1, $2)`
- _, err = db.Exec(query, e.Id, obj.Id)
-
- if err != nil{
+ _, err = db.Exec(query, e.Id, obj.Id)
+
+ if err != nil {
fmt.Println("error inserting replies cache")
- panic(err)
+ panic(err)
}
if !IsObjectLocal(db, e.Id) {
@@ -280,7 +280,7 @@ func WriteObjectReplyCache(db *sql.DB, obj ObjectBase) {
}
}
- return
+ return
}
}
@@ -293,7 +293,7 @@ func WriteActorToCache(db *sql.DB, actorID string) {
}
}
-func DeleteActorCache(db *sql.DB, actorID string) {
+func DeleteActorCache(db *sql.DB, actorID string) {
query := `select id from cacheactivitystream where id in (select id from cacheactivitystream where actor=$1)`
rows, err := db.Query(query, actorID)
diff --git a/client.go b/client.go
index 16d88bc..dc3ed36 100644
--- a/client.go
+++ b/client.go
@@ -1,16 +1,17 @@
package main
import (
- "net/http"
- "html/template"
"database/sql"
+ "fmt"
_ "github.com/lib/pq"
- "strings"
- "strconv"
- "sort"
+ "html/template"
+ "log"
+ "net/http"
"regexp"
+ "sort"
+ "strconv"
+ "strings"
"time"
- "fmt"
)
var Key *string = new(string)
@@ -19,86 +20,107 @@ var FollowingBoards []ObjectBase
var Boards []Board
-type Board struct{
- Name string
- Actor Actor
- Summary string
- PrefName string
- InReplyTo string
- Location string
- To string
- RedirectTo string
- Captcha string
+type Board struct {
+ Name string
+ Actor Actor
+ Summary string
+ PrefName string
+ InReplyTo string
+ Location string
+ To string
+ RedirectTo string
+ Captcha string
CaptchaCode string
- ModCred string
- Domain string
- TP string
- Restricted bool
- Post ObjectBase
+ ModCred string
+ Domain string
+ TP string
+ Restricted bool
+ Post ObjectBase
}
type PageData struct {
- Title string
+ Title string
PreferredUsername string
- Board Board
- Pages []int
- CurrentPage int
- TotalPage int
- Boards []Board
- Posts []ObjectBase
- Key string
- PostId string
- Instance Actor
- InstanceIndex []ObjectBase
- ReturnTo string
- NewsItems []NewsItem
- BoardRemainer []int
+ Board Board
+ Pages []int
+ CurrentPage int
+ TotalPage int
+ Boards []Board
+ Posts []ObjectBase
+ Key string
+ PostId string
+ Instance Actor
+ InstanceIndex []ObjectBase
+ ReturnTo string
+ NewsItems []NewsItem
+ BoardRemainer []int
+ Themes *[]string
}
type AdminPage struct {
- Title string
- Board Board
- Key string
- Actor string
- Boards []Board
- Following []string
- Followers []string
- Reported []Report
- Domain string
- IsLocal bool
+ Title string
+ Board Board
+ Key string
+ Actor string
+ Boards []Board
+ Following []string
+ Followers []string
+ Reported []Report
+ Domain string
+ IsLocal bool
PostBlacklist []PostBlacklist
AutoSubscribe bool
+ Themes *[]string
}
type Report struct {
- ID string
- Count int
+ ID string
+ Count int
Reason string
}
type Removed struct {
- ID string
- Type string
+ ID string
+ Type string
Board string
}
-
type NewsItem struct {
- Title string
+ Title string
Content template.HTML
- Time int
+ Time int
}
type PostBlacklist struct {
- Id int
+ Id int
Regex string
}
+func mod(i, j int) bool {
+ return i%j == 0
+}
+
+func sub(i, j int) int {
+ return i - j
+}
+
+func unixToReadable(u int) string {
+ return time.Unix(int64(u), 0).Format("Jan 02, 2006")
+}
+
+func timeToReadableLong(t time.Time) string {
+ return t.Format("01/02/06(Mon)15:04:05")
+}
+
+func timeToUnix(t time.Time) string {
+ return fmt.Sprint(t.Unix())
+}
+
func IndexGet(w http.ResponseWriter, r *http.Request, db *sql.DB) {
t := template.Must(template.New("").Funcs(template.FuncMap{
- "mod": func(i, j int) bool { return i%j == 0 },
- "sub": func (i, j int) int { return i - j },
- "unixtoreadable": func(u int) string { return time.Unix(int64(u), 0).Format("Jan 02, 2006") }}).ParseFiles("./static/main.html", "./static/index.html"))
+ "mod": mod,
+ "sub": sub,
+ "unixtoreadable": unixToReadable}).ParseFiles("./static/main.html", "./static/index.html"))
actor := GetActorFromDB(db, Domain)
@@ -115,21 +137,27 @@ func IndexGet(w http.ResponseWriter, r *http.Request, db *sql.DB) {
data.Board.Restricted = actor.Restricted
//almost certainly there is a better algorithm for this but the old one was wrong
//and I suck at math. This works at least.
- data.BoardRemainer = make([]int, 3-(len(data.Boards) % 3))
- if(len(data.BoardRemainer) == 3){
+ data.BoardRemainer = make([]int, 3-(len(data.Boards)%3))
+ if len(data.BoardRemainer) == 3 {
data.BoardRemainer = make([]int, 0)
}
//data.InstanceIndex = GetCollectionFromReq("https://fchan.xyz/followers").Items
data.NewsItems = getNewsFromDB(db, 3)
- t.ExecuteTemplate(w, "layout", data)
+ data.Themes = &Themes
+
+ err := t.ExecuteTemplate(w, "layout", data)
+ if err != nil {
+ // TODO: actual error handler
+ log.Printf("IndexGet: %s\n", err)
+ }
}
func NewsGet(w http.ResponseWriter, r *http.Request, db *sql.DB, timestamp int) {
t := template.Must(template.New("").Funcs(template.FuncMap{
- "sub": func (i, j int) int { return i - j },
- "unixtoreadable": func(u int) string { return time.Unix(int64(u), 0).Format("Jan 02, 2006") }}).ParseFiles("./static/main.html", "./static/news.html"))
+ "sub": sub,
+ "unixtoreadable": unixToReadable}).ParseFiles("./static/main.html", "./static/news.html"))
actor := GetActorFromDB(db, Domain)
@@ -156,14 +184,20 @@ func NewsGet(w http.ResponseWriter, r *http.Request, db *sql.DB, timestamp int)
data.Title = actor.PreferredUsername + ": " + data.NewsItems[0].Title
- t.ExecuteTemplate(w, "layout", data)
+ data.Themes = &Themes
+
+ err = t.ExecuteTemplate(w, "layout", data)
+ if err != nil {
+ // TODO: actual error handler
+ log.Printf("NewsGet: %s\n", err)
+ }
}
func AllNewsGet(w http.ResponseWriter, r *http.Request, db *sql.DB) {
t := template.Must(template.New("").Funcs(template.FuncMap{
- "mod": func(i, j int) bool { return i%j == 0 },
- "sub": func (i, j int) int { return i - j },
- "unixtoreadable": func(u int) string { return time.Unix(int64(u), 0).Format("Jan 02, 2006") }}).ParseFiles("./static/main.html", "./static/anews.html"))
+ "mod": mod,
+ "sub": sub,
+ "unixtoreadable": unixToReadable}).ParseFiles("./static/main.html", "./static/anews.html"))
actor := GetActorFromDB(db, Domain)
@@ -180,10 +214,16 @@ func AllNewsGet(w http.ResponseWriter, r *http.Request, db *sql.DB) {
data.Board.Restricted = actor.Restricted
data.NewsItems = getNewsFromDB(db, 0)
- t.ExecuteTemplate(w, "layout", data)
+ data.Themes = &Themes
+
+ err := t.ExecuteTemplate(w, "layout", data)
+ if err != nil {
+ // TODO: actual error handler
+ log.Printf("AllNewsGet: %s\n", err)
+ }
}
-func OutboxGet(w http.ResponseWriter, r *http.Request, db *sql.DB, collection Collection){
+func OutboxGet(w http.ResponseWriter, r *http.Request, db *sql.DB, collection Collection) {
t := template.Must(template.New("").Funcs(template.FuncMap{
"proxy": func(url string) string {
return MediaProxy(url)
@@ -198,33 +238,34 @@ func OutboxGet(w http.ResponseWriter, r *http.Request, db *sql.DB, collection Co
return ParseContent(db, board, op, content, thread)
},
"shortImg": func(url string) string {
- return ShortImg(url)
+ return ShortImg(url)
},
"convertSize": func(size int64) string {
- return ConvertSize(size)
+ return ConvertSize(size)
},
"isOnion": func(url string) bool {
- return IsOnion(url)
+ return IsOnion(url)
},
"showArchive": func() bool {
col := GetActorCollectionDBTypeLimit(db, collection.Actor.Id, "Archive", 1)
if len(col.OrderedItems) > 0 {
- return true
+ return true
}
- return false;
+ return false
},
"parseReplyLink": func(actorId string, op string, id string, content string) template.HTML {
actor := FingerActor(actorId)
title := strings.ReplaceAll(ParseLinkTitle(actor.Id, op, content), `/\&lt;`, ">")
- link := "<a href=\"" + actor.Name + "/" + shortURL(actor.Outbox, op) + "#" + shortURL(actor.Outbox, id) + "\" title=\"" + title + "\">&gt;&gt;" + shortURL(actor.Outbox, id) + "</a>"
+ link := "<a href=\"" + actor.Name + "/" + shortURL(actor.Outbox, op) + "#" + shortURL(actor.Outbox, id) + "\" title=\"" + title + "\" class=\"replyLink\">&gt;&gt;" + shortURL(actor.Outbox, id) + "</a>"
return template.HTML(link)
},
- "add": func (i, j int) int {
+ "add": func(i, j int) int {
return i + j
},
- "sub": func (i, j int) int { return i - j }}).ParseFiles("./static/main.html", "./static/nposts.html", "./static/top.html", "./static/bottom.html", "./static/posts.html"))
-
+ "timeToReadableLong": timeToReadableLong,
+ "timeToUnix": timeToUnix,
+ "sub": sub}).ParseFiles("./static/main.html", "./static/nposts.html", "./static/top.html", "./static/bottom.html", "./static/posts.html"))
actor := collection.Actor
@@ -273,10 +314,16 @@ func OutboxGet(w http.ResponseWriter, r *http.Request, db *sql.DB, collection Co
returnData.Pages = pages
returnData.TotalPage = len(returnData.Pages) - 1
- t.ExecuteTemplate(w, "layout", returnData)
+ returnData.Themes = &Themes
+
+ err := t.ExecuteTemplate(w, "layout", returnData)
+ if err != nil {
+ // TODO: actual error handler
+ log.Printf("OutboxGet: %s\n", err)
+ }
}
-func CatalogGet(w http.ResponseWriter, r *http.Request, db *sql.DB, collection Collection){
+func CatalogGet(w http.ResponseWriter, r *http.Request, db *sql.DB, collection Collection) {
t := template.Must(template.New("").Funcs(template.FuncMap{
"proxy": func(url string) string {
return MediaProxy(url)
@@ -288,17 +335,17 @@ func CatalogGet(w http.ResponseWriter, r *http.Request, db *sql.DB, collection C
return ParseAttachment(obj, catalog)
},
"isOnion": func(url string) bool {
- return IsOnion(url)
+ return IsOnion(url)
},
"showArchive": func() bool {
col := GetActorCollectionDBTypeLimit(db, collection.Actor.Id, "Archive", 1)
if len(col.OrderedItems) > 0 {
- return true
+ return true
}
- return false;
+ return false
},
- "sub": func (i, j int) int { return i - j }}).ParseFiles("./static/main.html", "./static/ncatalog.html", "./static/top.html"))
+ "sub": sub}).ParseFiles("./static/main.html", "./static/ncatalog.html", "./static/top.html"))
actor := collection.Actor
@@ -328,10 +375,16 @@ func CatalogGet(w http.ResponseWriter, r *http.Request, db *sql.DB, collection C
returnData.Posts = collection.OrderedItems
- t.ExecuteTemplate(w, "layout", returnData)
+ returnData.Themes = &Themes
+
+ err := t.ExecuteTemplate(w, "layout", returnData)
+ if err != nil {
+ // TODO: actual error handler
+ log.Printf("CatalogGet: %s\n", err)
+ }
}
-func ArchiveGet(w http.ResponseWriter, r *http.Request, db *sql.DB, collection Collection){
+func ArchiveGet(w http.ResponseWriter, r *http.Request, db *sql.DB, collection Collection) {
t := template.Must(template.New("").Funcs(template.FuncMap{
"proxy": func(url string) string {
return MediaProxy(url)
@@ -345,8 +398,8 @@ func ArchiveGet(w http.ResponseWriter, r *http.Request, db *sql.DB, collection C
"parseAttachment": func(obj ObjectBase, catalog bool) template.HTML {
return ParseAttachment(obj, catalog)
},
- "mod": func(i, j int) bool { return i % j == 0 },
- "sub": func (i, j int) int { return i - j }}).ParseFiles("./static/main.html", "./static/archive.html", "./static/bottom.html"))
+ "mod": mod,
+ "sub": sub}).ParseFiles("./static/main.html", "./static/archive.html", "./static/bottom.html"))
actor := collection.Actor
@@ -376,10 +429,16 @@ func ArchiveGet(w http.ResponseWriter, r *http.Request, db *sql.DB, collection C
returnData.Posts = collection.OrderedItems
- t.ExecuteTemplate(w, "layout", returnData)
+ returnData.Themes = &Themes
+
+ err := t.ExecuteTemplate(w, "layout", returnData)
+ if err != nil {
+ // TODO: actual error handler
+ log.Printf("ArchiveGet: %s\n", err)
+ }
}
-func PostGet(w http.ResponseWriter, r *http.Request, db *sql.DB){
+func PostGet(w http.ResponseWriter, r *http.Request, db *sql.DB) {
t := template.Must(template.New("").Funcs(template.FuncMap{
"proxy": func(url string) string {
return MediaProxy(url)
@@ -394,21 +453,23 @@ func PostGet(w http.ResponseWriter, r *http.Request, db *sql.DB){
return ParseContent(db, board, op, content, thread)
},
"shortImg": func(url string) string {
- return ShortImg(url)
+ return ShortImg(url)
},
"convertSize": func(size int64) string {
- return ConvertSize(size)
+ return ConvertSize(size)
},
"isOnion": func(url string) bool {
- return IsOnion(url)
+ return IsOnion(url)
},
"parseReplyLink": func(actorId string, op string, id string, content string) template.HTML {
actor := FingerActor(actorId)
title := strings.ReplaceAll(ParseLinkTitle(actor.Id, op, content), `/\&lt;`, ">")
- link := "<a href=\"" + actor.Name + "/" + shortURL(actor.Outbox, op) + "#" + shortURL(actor.Outbox, id) + "\" title=\"" + title + "\">&gt;&gt;" + shortURL(actor.Outbox, id) + "</a>"
+ link := "<a href=\"" + actor.Name + "/" + shortURL(actor.Outbox, op) + "#" + shortURL(actor.Outbox, id) + "\" title=\"" + title + "\" class=\"replyLink\">&gt;&gt;" + shortURL(actor.Outbox, id) + "</a>"
return template.HTML(link)
},
- "sub": func (i, j int) int { return i - j }}).ParseFiles("./static/main.html", "./static/npost.html", "./static/top.html", "./static/bottom.html", "./static/posts.html"))
+ "timeToReadableLong": timeToReadableLong,
+ "timeToUnix": timeToUnix,
+ "sub": sub}).ParseFiles("./static/main.html", "./static/npost.html", "./static/top.html", "./static/bottom.html", "./static/posts.html"))
path := r.URL.Path
actor := GetActorFromPath(db, path, "/")
@@ -469,7 +530,13 @@ func PostGet(w http.ResponseWriter, r *http.Request, db *sql.DB){
returnData.PostId = shortURL(returnData.Board.To, returnData.Posts[0].Id)
}
- t.ExecuteTemplate(w, "layout", returnData)
+ returnData.Themes = &Themes
+
+ err := t.ExecuteTemplate(w, "layout", returnData)
+ if err != nil {
+ // TODO: actual error handler
+ log.Printf("PostGet: %s\n", err)
+ }
}
func GetBoardCollection(db *sql.DB) []Board {
@@ -565,7 +632,7 @@ func GetCaptchaCode(captcha string) string {
return code
}
-func CreateLocalDeleteDB(db *sql.DB, id string, _type string) {
+func CreateLocalDeleteDB(db *sql.DB, id string, _type string) {
query := `select id from removed where id=$1`
rows, err := db.Query(query, id)
@@ -681,7 +748,7 @@ func CloseLocalReportDB(db *sql.DB, id string, board string) {
CheckError(err, "Could not delete local report from db")
}
-func GetActorFollowNameFromPath(path string) string{
+func GetActorFollowNameFromPath(path string) string {
var actor string
re := regexp.MustCompile("f\\w+-")
@@ -709,11 +776,11 @@ func GetActorsFollowFromName(actor Actor, name string) []string {
return followingActors
}
-func GetActorsFollowPostFromId(db *sql.DB, actors []string, id string) Collection{
+func GetActorsFollowPostFromId(db *sql.DB, actors []string, id string) Collection {
var collection Collection
for _, e := range actors {
- tempCol := GetObjectByIDFromDB(db, e + "/" + id)
+ tempCol := GetObjectByIDFromDB(db, e+"/"+id)
if len(tempCol.OrderedItems) > 0 {
collection = tempCol
return collection
@@ -724,19 +791,22 @@ func GetActorsFollowPostFromId(db *sql.DB, actors []string, id string) Collectio
}
type ObjectBaseSortDesc []ObjectBase
-func (a ObjectBaseSortDesc) Len() int { return len(a) }
-func (a ObjectBaseSortDesc) Less(i, j int) bool { return a[i].Updated > a[j].Updated }
-func (a ObjectBaseSortDesc) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+
+func (a ObjectBaseSortDesc) Len() int { return len(a) }
+func (a ObjectBaseSortDesc) Less(i, j int) bool { return a[i].Updated.After(a[j].Updated) }
+func (a ObjectBaseSortDesc) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type ObjectBaseSortAsc []ObjectBase
-func (a ObjectBaseSortAsc) Len() int { return len(a) }
-func (a ObjectBaseSortAsc) Less(i, j int) bool { return a[i].Published < a[j].Published }
-func (a ObjectBaseSortAsc) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+
+func (a ObjectBaseSortAsc) Len() int { return len(a) }
+func (a ObjectBaseSortAsc) Less(i, j int) bool { return a[i].Published.Before(a[j].Published) }
+func (a ObjectBaseSortAsc) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type BoardSortAsc []Board
-func (a BoardSortAsc) Len() int { return len(a) }
+
+func (a BoardSortAsc) Len() int { return len(a) }
func (a BoardSortAsc) Less(i, j int) bool { return a[i].Name < a[j].Name }
-func (a BoardSortAsc) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a BoardSortAsc) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func MediaProxy(url string) string {
re := regexp.MustCompile("(.+)?" + Domain + "(.+)?")
@@ -762,7 +832,7 @@ func ParseAttachment(obj ObjectBase, catalog bool) template.HTML {
}
var media string
- if(regexp.MustCompile(`image\/`).MatchString(obj.Attachment[0].MediaType)){
+ if regexp.MustCompile(`image\/`).MatchString(obj.Attachment[0].MediaType) {
media = "<img "
media += "id=\"img\" "
media += "main=\"1\" "
@@ -786,7 +856,7 @@ func ParseAttachment(obj ObjectBase, catalog bool) template.HTML {
return template.HTML(media)
}
- if(regexp.MustCompile(`audio\/`).MatchString(obj.Attachment[0].MediaType)){
+ if regexp.MustCompile(`audio\/`).MatchString(obj.Attachment[0].MediaType) {
media = "<audio "
media += "controls=\"controls\" "
media += "preload=\"metadta\" "
@@ -806,7 +876,7 @@ func ParseAttachment(obj ObjectBase, catalog bool) template.HTML {
return template.HTML(media)
}
- if(regexp.MustCompile(`video\/`).MatchString(obj.Attachment[0].MediaType)){
+ if regexp.MustCompile(`video\/`).MatchString(obj.Attachment[0].MediaType) {
media = "<video "
media += "controls=\"controls\" "
media += "preload=\"metadta\" "
@@ -841,7 +911,7 @@ func ParseContent(db *sql.DB, board Actor, op string, content string, thread Obj
nContent = strings.ReplaceAll(nContent, `/\&lt;`, ">")
return template.HTML(nContent)
-};
+}
func ParseLinkComments(db *sql.DB, board Actor, op string, content string, thread ObjectBase) string {
re := regexp.MustCompile(`(>>(https?://[A-Za-z0-9_.:\-~]+\/[A-Za-z0-9_.\-~]+\/)(f[A-Za-z0-9_.\-~]+-)?([A-Za-z0-9_.\-~]+)?#?([A-Za-z0-9_.\-~]+)?)`)
@@ -885,17 +955,12 @@ func ParseLinkComments(db *sql.DB, board Actor, op string, content string, threa
}
}
- var style string
- if board.Restricted {
- style = "color: #af0a0f;"
- }
-
//replace link with quote format
replyID, isReply := IsReplyToOP(db, op, parsedLink)
if isReply {
id := shortURL(board.Outbox, replyID)
- content = strings.Replace(content, match[i][0], "<a class=\"reply\" style=\"" + style + "\" title=\"" + quoteTitle + "\" href=\"/" + board.Name + "/" + shortURL(board.Outbox, op) + "#" + id + "\">&gt;&gt;" + id + "" + isOP + "</a>", -1)
+ content = strings.Replace(content, match[i][0], "<a class=\"reply\" title=\""+quoteTitle+"\" href=\"/"+board.Name+"/"+shortURL(board.Outbox, op)+"#"+id+"\">&gt;&gt;"+id+""+isOP+"</a>", -1)
} else {
@@ -908,7 +973,7 @@ func ParseLinkComments(db *sql.DB, board Actor, op string, content string, threa
}
if actor.Id != "" {
- content = strings.Replace(content, match[i][0], "<a class=\"reply\" style=\"" + style + "\" title=\"" + quoteTitle + "\" href=\"" + link + "\">&gt;&gt;" + shortURL(board.Outbox, parsedLink) + isOP + " →</a>", -1)
+ content = strings.Replace(content, match[i][0], "<a class=\"reply\" title=\""+quoteTitle+"\" href=\""+link+"\">&gt;&gt;"+shortURL(board.Outbox, parsedLink)+isOP+" →</a>", -1)
}
}
}
@@ -931,7 +996,7 @@ func ParseLinkTitle(actorName string, op string, content string) string {
}
link = ConvertHashLink(domain, link)
- content = strings.Replace(content, match[i][0], ">>" + shortURL(actorName, link) + isOP , 1)
+ content = strings.Replace(content, match[i][0], ">>"+shortURL(actorName, link)+isOP, 1)
}
content = strings.ReplaceAll(content, "'", "")
@@ -948,7 +1013,7 @@ func ParseCommentQuotes(content string) string {
for i, _ := range match {
quote := strings.Replace(match[i][0], ">", "&gt;", 1)
- line := re.ReplaceAllString(match[i][0], "<span class=\"quote\">" + quote + "</span>")
+ line := re.ReplaceAllString(match[i][0], "<span class=\"quote\">"+quote+"</span>")
content = strings.Replace(content, match[i][0], line, 1)
}
@@ -979,7 +1044,7 @@ func ShortImg(url string) string {
fileName := re.ReplaceAllString(url, "")
- if(len(fileName) > 26) {
+ if len(fileName) > 26 {
re := regexp.MustCompile(`(^.{26})`)
match := re.FindStringSubmatch(fileName)
@@ -993,35 +1058,35 @@ func ShortImg(url string) string {
match = re.FindStringSubmatch(url)
if len(match) > 0 {
- nURL = nURL + "(...)" + match[0];
+ nURL = nURL + "(...)" + match[0]
}
}
- return nURL;
+ return nURL
}
func ConvertSize(size int64) string {
var rValue string
- convert := float32(size) / 1024.0;
+ convert := float32(size) / 1024.0
- if(convert > 1024) {
- convert = convert / 1024.0;
+ if convert > 1024 {
+ convert = convert / 1024.0
rValue = fmt.Sprintf("%.2f MB", convert)
} else {
rValue = fmt.Sprintf("%.2f KB", convert)
}
- return rValue;
+ return rValue
}
func ShortExcerpt(post ObjectBase) string {
var returnString string
if post.Name != "" {
- returnString = post.Name + "| " + post.Content;
+ returnString = post.Name + "| " + post.Content
} else {
- returnString = post.Content;
+ returnString = post.Content
}
re := regexp.MustCompile(`(^(.|\r\n|\n){100})`)
@@ -1037,7 +1102,7 @@ func ShortExcerpt(post ObjectBase) string {
match = re.FindStringSubmatch(returnString)
if len(match) > 0 {
- returnString = strings.Replace(returnString, match[0], "<b>" + match[0] + "</b>", 1)
+ returnString = strings.Replace(returnString, match[0], "<b>"+match[0]+"</b>", 1)
returnString = strings.Replace(returnString, "|", ":", 1)
}
@@ -1046,8 +1111,8 @@ func ShortExcerpt(post ObjectBase) string {
func IsOnion(url string) bool {
re := regexp.MustCompile(`\.onion`)
- if(re.MatchString(url)) {
- return true;
+ if re.MatchString(url) {
+ return true
}
return false
diff --git a/config-init b/config-init
index eb71163..426e652 100644
--- a/config-init
+++ b/config-init
@@ -35,4 +35,7 @@ publicindex:false
## add your instance salt here for secure tripcodes
-instancesalt: \ No newline at end of file
+instancesalt:
+
+## connect to this redis server
+redis:redis://localhost
diff --git a/config-init.docker b/config-init.docker
new file mode 100644
index 0000000..6a8b18a
--- /dev/null
+++ b/config-init.docker
@@ -0,0 +1,42 @@
+instance:fchan.xyz
+instanceport:3000
+instancename:FChan
+instancesummary:FChan is a federated image board instance.
+
+## For `instancetp` if you plan to support https
+## make sure you setup the ssl certs before running the server initially
+## do not start with http:// and then switch to https://
+## this will cause issues if switched from on protocol to the other.
+## If you do, the database entries will have to be converted to support the change
+## this will cause a lot of headaches if switched back and forth.
+## Choose which one you are going to support and do not change it for best results.
+
+instancetp:http://
+
+## postgres is at postgres and is setup with default credentials
+dbhost:postgres
+dbport:5432
+dbname:fchan
+dbuser:fchan
+dbpass:hackme
+
+emailserver:
+emailport:
+emailaddress:
+emailpass:
+
+## enter proxy ip and port if you want to have tor connections supported
+## 127.0.0.1:9050 default
+
+torproxy:
+
+## Change to true if you want your instance to be added to the public instance index
+
+publicindex:false
+
+## add your instance salt here for secure tripcodes
+
+instancesalt:
+
+## we have redis at "redis", so...
+redis:redis://redis
diff --git a/database.go b/database.go
index 9c68364..b0a5736 100644
--- a/database.go
+++ b/database.go
@@ -3,20 +3,20 @@ package main
import (
"database/sql"
"fmt"
+ "html/template"
"os"
+ "regexp"
"sort"
"strings"
- "regexp"
"time"
- "html/template"
_ "github.com/lib/pq"
)
func GetActorFromDB(db *sql.DB, id string) Actor {
- var nActor Actor
+ var nActor Actor
- query :=`select type, id, name, preferedusername, inbox, outbox, following, followers, restricted, summary, publickeypem from actor where id=$1`
+ query := `select type, id, name, preferedusername, inbox, outbox, following, followers, restricted, summary, publickeypem from actor where id=$1`
rows, err := db.Query(query, id)
@@ -32,7 +32,7 @@ func GetActorFromDB(db *sql.DB, id string) Actor {
}
nActor.PublicKey = GetActorPemFromDB(db, publicKeyPem)
- if nActor.Id != "" && nActor.PublicKey.PublicKeyPem == ""{
+ if nActor.Id != "" && nActor.PublicKey.PublicKeyPem == "" {
err = CreatePublicKeyFromPrivate(db, &nActor, publicKeyPem)
CheckError(err, "error creating public key from private")
}
@@ -43,7 +43,7 @@ func GetActorFromDB(db *sql.DB, id string) Actor {
func GetActorByNameFromDB(db *sql.DB, name string) Actor {
var nActor Actor
- query :=`select type, id, name, preferedusername, inbox, outbox, following, followers, restricted, summary, publickeypem from actor where name=$1`
+ query := `select type, id, name, preferedusername, inbox, outbox, following, followers, restricted, summary, publickeypem from actor where name=$1`
rows, err := db.Query(query, name)
@@ -58,7 +58,7 @@ func GetActorByNameFromDB(db *sql.DB, name string) Actor {
CheckError(err, "error with actor from db scan ")
}
- if nActor.Id != "" && nActor.PublicKey.PublicKeyPem == ""{
+ if nActor.Id != "" && nActor.PublicKey.PublicKeyPem == "" {
err = CreatePublicKeyFromPrivate(db, &nActor, publicKeyPem)
CheckError(err, "error creating public key from private")
}
@@ -66,7 +66,7 @@ func GetActorByNameFromDB(db *sql.DB, name string) Actor {
return nActor
}
-func CreateNewBoardDB(db *sql.DB, actor Actor) Actor{
+func CreateNewBoardDB(db *sql.DB, actor Actor) Actor {
query := `insert into actor (type, id, name, preferedusername, inbox, outbox, following, followers, summary, restricted) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`
@@ -77,7 +77,7 @@ func CreateNewBoardDB(db *sql.DB, actor Actor) Actor{
} else {
fmt.Println("board added")
for _, e := range actor.AuthRequirement {
- query = `insert into actorauth (type, board) values ($1, $2)`
+ query = `insert into actorauth (type, board) values ($1, $2)`
_, err := db.Exec(query, e, actor.Name)
CheckError(err, "auth exists")
}
@@ -85,20 +85,20 @@ func CreateNewBoardDB(db *sql.DB, actor Actor) Actor{
var verify Verify
verify.Identifier = actor.Id
- verify.Code = CreateKey(50)
- verify.Type = "admin"
+ verify.Code = CreateKey(50)
+ verify.Type = "admin"
CreateVerification(db, verify)
verify.Identifier = actor.Id
- verify.Code = CreateKey(50)
- verify.Type = "janitor"
+ verify.Code = CreateKey(50)
+ verify.Type = "janitor"
CreateVerification(db, verify)
verify.Identifier = actor.Id
- verify.Code = CreateKey(50)
- verify.Type = "post"
+ verify.Code = CreateKey(50)
+ verify.Type = "post"
CreateVerification(db, verify)
@@ -155,12 +155,12 @@ func GetBoards(db *sql.DB) []Actor {
CheckError(err, "could not get boards from db query")
defer rows.Close()
- for rows.Next(){
+ for rows.Next() {
var actor = new(Actor)
err = rows.Scan(&actor.Type, &actor.Id, &actor.Name, &actor.PreferredUsername, &actor.Inbox, &actor.Outbox, &actor.Following, &actor.Followers)
- if err !=nil{
+ if err != nil {
panic(err)
}
@@ -175,16 +175,16 @@ func WriteObjectToDB(db *sql.DB, obj ObjectBase) ObjectBase {
if len(obj.Attachment) > 0 {
if obj.Preview.Href != "" {
obj.Preview.Id = fmt.Sprintf("%s/%s", obj.Actor, CreateUniqueID(db, obj.Actor))
- obj.Preview.Published = time.Now().UTC().Format(time.RFC3339)
- obj.Preview.Updated = time.Now().UTC().Format(time.RFC3339)
+ obj.Preview.Published = time.Now().UTC()
+ obj.Preview.Updated = time.Now().UTC()
obj.Preview.AttributedTo = obj.Id
WritePreviewToDB(db, *obj.Preview)
}
for i, _ := range obj.Attachment {
obj.Attachment[i].Id = fmt.Sprintf("%s/%s", obj.Actor, CreateUniqueID(db, obj.Actor))
- obj.Attachment[i].Published = time.Now().UTC().Format(time.RFC3339)
- obj.Attachment[i].Updated = time.Now().UTC().Format(time.RFC3339)
+ obj.Attachment[i].Published = time.Now().UTC()
+ obj.Attachment[i].Updated = time.Now().UTC()
obj.Attachment[i].AttributedTo = obj.Id
WriteAttachmentToDB(db, obj.Attachment[i])
WriteActivitytoDBWithAttachment(db, obj, obj.Attachment[i], *obj.Preview)
@@ -205,7 +205,7 @@ func WriteObjectUpdatesToDB(db *sql.DB, obj ObjectBase) {
_, e := db.Exec(query, time.Now().UTC().Format(time.RFC3339), obj.Id)
- if e != nil{
+ if e != nil {
fmt.Println("error inserting updating inreplyto")
panic(e)
}
@@ -214,7 +214,7 @@ func WriteObjectUpdatesToDB(db *sql.DB, obj ObjectBase) {
_, e = db.Exec(query, time.Now().UTC().Format(time.RFC3339), obj.Id)
- if e != nil{
+ if e != nil {
fmt.Println("error inserting updating cache inreplyto")
panic(e)
}
@@ -229,7 +229,7 @@ func WriteObjectReplyToLocalDB(db *sql.DB, id string, replyto string) {
query = `select inreplyto from replies where id=$1`
- rows, err := db.Query(query,replyto)
+ rows, err := db.Query(query, replyto)
CheckError(err, "Could not query select inreplyto")
@@ -260,7 +260,6 @@ func WriteObjectReplyToDB(db *sql.DB, obj ObjectBase) {
}
}
-
query := `select id from replies where id=$1 and inreplyto=$2`
rows, err := db.Query(query, obj.Id, e.Id)
@@ -336,7 +335,6 @@ func WriteActorObjectReplyToDB(db *sql.DB, obj ObjectBase) {
_, err := db.Exec(query, obj.Id, e.Id)
-
CheckError(err, "error inserting replies db")
}
}
@@ -370,7 +368,7 @@ func WriteWalletToDB(db *sql.DB, obj ObjectBase) {
for _, e := range obj.Wallet {
query := `insert into wallet (id, type, address) values ($1, $2, $3)`
- _, err := db.Exec(query, obj.Id ,e.Type, e.Address)
+ _, err := db.Exec(query, obj.Id, e.Type, e.Address)
CheckError(err, "error with write wallet query")
}
@@ -387,9 +385,9 @@ func WriteActivitytoDB(db *sql.DB, obj ObjectBase) {
query := `insert into activitystream (id, type, name, content, published, updated, attributedto, actor, tripcode, sensitive) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`
- _, e := db.Exec(query, obj.Id ,obj.Type, obj.Name, obj.Content, obj.Published, obj.Updated, obj.AttributedTo, obj.Actor, obj.TripCode, obj.Sensitive)
+ _, e := db.Exec(query, obj.Id, obj.Type, obj.Name, obj.Content, obj.Published, obj.Updated, obj.AttributedTo, obj.Actor, obj.TripCode, obj.Sensitive)
- if e != nil{
+ if e != nil {
fmt.Println("error inserting new activity")
panic(e)
}
@@ -403,9 +401,9 @@ func WriteActivitytoDBWithAttachment(db *sql.DB, obj ObjectBase, attachment Obje
query := `insert into activitystream (id, type, name, content, attachment, preview, published, updated, attributedto, actor, tripcode, sensitive) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`
- _, e := db.Exec(query, obj.Id ,obj.Type, obj.Name, obj.Content, attachment.Id, preview.Id, obj.Published, obj.Updated, obj.AttributedTo, obj.Actor, obj.TripCode, obj.Sensitive)
+ _, e := db.Exec(query, obj.Id, obj.Type, obj.Name, obj.Content, attachment.Id, preview.Id, obj.Published, obj.Updated, obj.AttributedTo, obj.Actor, obj.TripCode, obj.Sensitive)
- if e != nil{
+ if e != nil {
fmt.Println("error inserting new activity with attachment")
panic(e)
}
@@ -414,9 +412,9 @@ func WriteActivitytoDBWithAttachment(db *sql.DB, obj ObjectBase, attachment Obje
func WriteAttachmentToDB(db *sql.DB, obj ObjectBase) {
query := `insert into activitystream (id, type, name, href, published, updated, attributedTo, mediatype, size) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)`
- _, e := db.Exec(query, obj.Id ,obj.Type, obj.Name, obj.Href, obj.Published, obj.Updated, obj.AttributedTo, obj.MediaType, obj.Size)
+ _, e := db.Exec(query, obj.Id, obj.Type, obj.Name, obj.Href, obj.Published, obj.Updated, obj.AttributedTo, obj.MediaType, obj.Size)
- if e != nil{
+ if e != nil {
fmt.Println("error inserting new attachment")
panic(e)
}
@@ -425,9 +423,9 @@ func WriteAttachmentToDB(db *sql.DB, obj ObjectBase) {
func WritePreviewToDB(db *sql.DB, obj NestedObjectBase) {
query := `insert into activitystream (id, type, name, href, published, updated, attributedTo, mediatype, size) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)`
- _, e := db.Exec(query, obj.Id ,obj.Type, obj.Name, obj.Href, obj.Published, obj.Updated, obj.AttributedTo, obj.MediaType, obj.Size)
+ _, e := db.Exec(query, obj.Id, obj.Type, obj.Name, obj.Href, obj.Published, obj.Updated, obj.AttributedTo, obj.MediaType, obj.Size)
- if e != nil{
+ if e != nil {
fmt.Println("error inserting new attachment")
panic(e)
}
@@ -447,11 +445,11 @@ func GetActivityFromDB(db *sql.DB, id string) Collection {
CheckError(err, "error query object from db")
defer rows.Close()
- for rows.Next(){
+ for rows.Next() {
var post ObjectBase
var actor Actor
var attachID string
- var previewID string
+ var previewID string
err = rows.Scan(&nColl.Actor.Id, &post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.Updated, &post.AttributedTo, &attachID, &previewID, &actor.Id, &post.TripCode, &post.Sensitive)
@@ -484,13 +482,13 @@ func GetObjectFromDBPage(db *sql.DB, id string, page int) Collection {
query := `select count (x.id) over(), x.id, x.name, x.content, x.type, x.published, x.updated, x.attributedto, x.attachment, x.preview, x.actor, x.tripcode, x.sensitive from (select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from activitystream where actor=$1 and id in (select id from replies where inreplyto='') and type='Note' union select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from activitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note' union select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from cacheactivitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note') as x order by x.updated desc limit 15 offset $2`
- rows, err := db.Query(query, id, page * 15)
+ rows, err := db.Query(query, id, page*15)
CheckError(err, "error query object from db")
var count int
defer rows.Close()
- for rows.Next(){
+ for rows.Next() {
var post ObjectBase
var actor Actor
var attachID string
@@ -533,7 +531,7 @@ func GetActorObjectCollectionFromDB(db *sql.DB, actorId string) Collection {
CheckError(err, "error query object from db")
defer rows.Close()
- for rows.Next(){
+ for rows.Next() {
var post ObjectBase
var actor Actor
var attachID string
@@ -575,7 +573,7 @@ func GetObjectFromDB(db *sql.DB, id string) Collection {
CheckError(err, "error query object from db")
defer rows.Close()
- for rows.Next(){
+ for rows.Next() {
var post ObjectBase
var actor Actor
var attachID string
@@ -626,7 +624,7 @@ func GetObjectFromDBFromID(db *sql.DB, id string) Collection {
CheckError(err, "error query object from db")
defer rows.Close()
- for rows.Next(){
+ for rows.Next() {
var post ObjectBase
var actor Actor
var attachID string
@@ -668,7 +666,7 @@ func GetObjectFromDBCatalog(db *sql.DB, id string) Collection {
CheckError(err, "error query object from db")
defer rows.Close()
- for rows.Next(){
+ for rows.Next() {
var post ObjectBase
var actor Actor
var attachID string
@@ -709,7 +707,7 @@ func GetObjectByIDFromDB(db *sql.DB, postID string) Collection {
CheckError(err, "error query object from db")
defer rows.Close()
- for rows.Next(){
+ for rows.Next() {
var post ObjectBase
var actor Actor
var attachID string
@@ -987,7 +985,7 @@ func GetObjectAttachment(db *sql.DB, id string) []ObjectBase {
query := `select x.id, x.type, x.name, x.href, x.mediatype, x.size, x.published from (select id, type, name, href, mediatype, size, published from activitystream where id=$1 union select id, type, name, href, mediatype, size, published from cacheactivitystream where id=$1) as x`
- rows, err := db.Query(query, id)
+ rows, err := db.Query(query, id)
CheckError(err, "could not select object attachment query")
@@ -996,7 +994,7 @@ func GetObjectAttachment(db *sql.DB, id string) []ObjectBase {
var attachment = new(ObjectBase)
err = rows.Scan(&attachment.Id, &attachment.Type, &attachment.Name, &attachment.Href, &attachment.MediaType, &attachment.Size, &attachment.Published)
- if err !=nil{
+ if err != nil {
fmt.Println("error with attachment db query")
panic(err)
}
@@ -1025,7 +1023,7 @@ func GetObjectPreview(db *sql.DB, id string) *NestedObjectBase {
return &preview
}
-func GetObjectPostsTotalDB(db *sql.DB, actor Actor) int{
+func GetObjectPostsTotalDB(db *sql.DB, actor Actor) int {
count := 0
query := `select count(id) from activitystream where actor=$1 and id in (select id from replies where inreplyto='' and type='Note')`
@@ -1043,7 +1041,7 @@ func GetObjectPostsTotalDB(db *sql.DB, actor Actor) int{
return count
}
-func GetObjectImgsTotalDB(db *sql.DB, actor Actor) int{
+func GetObjectImgsTotalDB(db *sql.DB, actor Actor) int {
count := 0
query := `select count(attachment) from activitystream where actor=$1 and id in (select id from replies where inreplyto='' and type='Note' )`
@@ -1075,10 +1073,10 @@ func DeletePreviewFromFile(db *sql.DB, id string) {
var href string
err := rows.Scan(&href)
- href = strings.Replace(href, Domain + "/", "", 1)
+ href = strings.Replace(href, Domain+"/", "", 1)
CheckError(err, "error scanning delete attachment")
- if(href != "static/notfound.png") {
+ if href != "static/notfound.png" {
_, err = os.Stat(href)
if err == nil {
os.Remove(href)
@@ -1101,10 +1099,10 @@ func RemovePreviewFromFile(db *sql.DB, id string) {
var href string
err := rows.Scan(&href)
- href = strings.Replace(href, Domain + "/", "", 1)
+ href = strings.Replace(href, Domain+"/", "", 1)
CheckError(err, "error scanning delete attachment")
- if(href != "static/notfound.png") {
+ if href != "static/notfound.png" {
_, err = os.Stat(href)
if err == nil {
os.Remove(href)
@@ -1128,10 +1126,10 @@ func DeleteAttachmentFromFile(db *sql.DB, id string) {
var href string
err := rows.Scan(&href)
- href = strings.Replace(href, Domain + "/", "", 1)
+ href = strings.Replace(href, Domain+"/", "", 1)
CheckError(err, "error scanning delete preview")
- if(href != "static/notfound.png") {
+ if href != "static/notfound.png" {
_, err = os.Stat(href)
if err == nil {
os.Remove(href)
@@ -1186,13 +1184,13 @@ func TombstoneAttachmentFromDB(db *sql.DB, id string) {
var query = `update activitystream set type='Tombstone', mediatype='image/png', href=$1, name='', content='', attributedto='deleted', deleted=$2 where id in (select attachment from activitystream where id=$3)`
- _, err := db.Exec(query, Domain + "/static/notfound.png", datetime, id)
+ _, err := db.Exec(query, Domain+"/static/notfound.png", datetime, id)
CheckError(err, "error with tombstone attachment")
query = `update cacheactivitystream set type='Tombstone', mediatype='image/png', href=$1, name='', content='', attributedto='deleted', deleted=$2 where id in (select attachment from cacheactivitystream where id=$3)`
- _, err = db.Exec(query, Domain + "/static/notfound.png", datetime, id)
+ _, err = db.Exec(query, Domain+"/static/notfound.png", datetime, id)
CheckError(err, "error with tombstone cache attachment")
}
@@ -1216,13 +1214,13 @@ func TombstonePreviewFromDB(db *sql.DB, id string) {
var query = `update activitystream set type='Tombstone', mediatype='image/png', href=$1, name='', content='', attributedto='deleted', deleted=$2 where id in (select preview from activitystream where id=$3)`
- _, err := db.Exec(query, Domain + "/static/notfound.png", datetime, id)
+ _, err := db.Exec(query, Domain+"/static/notfound.png", datetime, id)
CheckError(err, "error with tombstone preview")
query = `update cacheactivitystream set type='Tombstone', mediatype='image/png', href=$1, name='', content='', attributedto='deleted', deleted=$2 where id in (select preview from cacheactivitystream where id=$3)`
- _, err = db.Exec(query, Domain + "/static/notfound.png", datetime, id)
+ _, err = db.Exec(query, Domain+"/static/notfound.png", datetime, id)
CheckError(err, "error with tombstone cache preview")
}
@@ -1241,7 +1239,7 @@ func DeletePreviewFromDB(db *sql.DB, id string) {
CheckError(err, "error with delete cache preview")
}
-func DeleteObjectRepliedTo(db *sql.DB, id string){
+func DeleteObjectRepliedTo(db *sql.DB, id string) {
query := `delete from replies where id=$1`
_, err := db.Exec(query, id)
@@ -1264,7 +1262,7 @@ func TombstoneObjectFromDB(db *sql.DB, id string) {
}
func DeleteObjectFromDB(db *sql.DB, id string) {
- var query = `delete from activitystream where id=$1`
+ var query = `delete from activitystream where id=$1`
_, err := db.Exec(query, id)
@@ -1393,18 +1391,18 @@ func SetObjectRepliesFromDB(db *sql.DB, id string, _type string) {
}
func SetObject(db *sql.DB, id string, _type string) {
- SetAttachmentFromDB(db, id, _type);
- SetPreviewFromDB(db, id, _type);
- SetObjectFromDB(db, id, _type);
+ SetAttachmentFromDB(db, id, _type)
+ SetPreviewFromDB(db, id, _type)
+ SetObjectFromDB(db, id, _type)
}
func SetObjectAndReplies(db *sql.DB, id string, _type string) {
- SetAttachmentFromDB(db, id, _type);
- SetPreviewFromDB(db, id, _type);
- SetObjectRepliesFromDB(db, id, _type);
- SetAttachmentRepliesFromDB(db, id, _type);
- SetPreviewRepliesFromDB(db, id, _type);
- SetObjectFromDB(db, id, _type);
+ SetAttachmentFromDB(db, id, _type)
+ SetPreviewFromDB(db, id, _type)
+ SetObjectRepliesFromDB(db, id, _type)
+ SetAttachmentRepliesFromDB(db, id, _type)
+ SetPreviewRepliesFromDB(db, id, _type)
+ SetObjectFromDB(db, id, _type)
}
func DeleteObject(db *sql.DB, id string) {
@@ -1438,7 +1436,7 @@ func TombstoneObjectAndReplies(db *sql.DB, id string) {
TombstoneObjectFromDB(db, id)
}
-func GetRandomCaptcha(db *sql.DB) string{
+func GetRandomCaptcha(db *sql.DB) string {
query := `select identifier from verification where type='captcha' order by random() limit 1`
rows, err := db.Query(query)
@@ -1457,7 +1455,7 @@ func GetRandomCaptcha(db *sql.DB) string{
return verify
}
-func GetCaptchaTotal(db *sql.DB) int{
+func GetCaptchaTotal(db *sql.DB) int {
query := `select count(*) from verification where type='captcha'`
rows, err := db.Query(query)
@@ -1467,8 +1465,8 @@ func GetCaptchaTotal(db *sql.DB) int{
defer rows.Close()
var count int
- for rows.Next(){
- if err := rows.Scan(&count); err != nil{
+ for rows.Next() {
+ if err := rows.Scan(&count); err != nil {
CheckError(err, "could not get captcha total")
}
}
@@ -1587,7 +1585,7 @@ func GetActorPemFromDB(db *sql.DB, pemID string) PublicKeyPem {
rows.Next()
rows.Scan(&pem.Id, &pem.Owner, &pem.PublicKeyPem)
f, err := os.ReadFile(pem.PublicKeyPem)
- if err != nil{
+ if err != nil {
pem.PublicKeyPem = ""
return pem
}
@@ -1597,7 +1595,7 @@ func GetActorPemFromDB(db *sql.DB, pemID string) PublicKeyPem {
return pem
}
-func GetActorPemFileFromDB(db *sql.DB, pemID string) string{
+func GetActorPemFileFromDB(db *sql.DB, pemID string) string {
query := `select file from publickeypem where id=$1`
rows, err := db.Query(query, pemID)
@@ -1629,21 +1627,20 @@ func getNewsFromDB(db *sql.DB, limit int) []NewsItem {
var news []NewsItem
var query string
- if(limit > 0) {
- query =`select title, content, time from newsItem order by time desc limit $1`
+ if limit > 0 {
+ query = `select title, content, time from newsItem order by time desc limit $1`
} else {
- query =`select title, content, time from newsItem order by time desc`
+ query = `select title, content, time from newsItem order by time desc`
}
var rows *sql.Rows
var err error
- if(limit > 0) {
+ if limit > 0 {
rows, err = db.Query(query, limit)
} else {
rows, err = db.Query(query)
}
-
if CheckError(err, "could not get news from db query") != nil {
return news
}
@@ -1755,10 +1752,10 @@ func DeleteRegexBlacklistDB(db *sql.DB, id int) {
CheckError(err, "error with delete from postblacklist")
}
-func GetActorAutoSubscribeDB(db *sql.DB, id string) bool{
+func GetActorAutoSubscribeDB(db *sql.DB, id string) bool {
query := `select autosubscribe from actor where id=$1`
- rows, err:= db.Query(query, id)
+ rows, err := db.Query(query, id)
CheckError(err, "error with getting actor auto subscribe status from db")
@@ -1767,7 +1764,6 @@ func GetActorAutoSubscribeDB(db *sql.DB, id string) bool{
rows.Next()
rows.Scan(&subscribed)
-
return subscribed
}
@@ -1802,12 +1798,12 @@ func AddInstanceToInactiveDB(db *sql.DB, instance string) {
} else {
if IsInactiveTimestamp(db, timeStamp) {
query := `delete from following where following like $1`
- _, err:= db.Exec(query, "%" + instance + "%")
+ _, err := db.Exec(query, "%"+instance+"%")
CheckError(err, "error deleting inactive instance following")
query = `delete from follower where follower like $1`
- _, err= db.Exec(query, "%" + instance + "%")
+ _, err = db.Exec(query, "%"+instance+"%")
CheckError(err, "error deleting inactive instance follower")
@@ -1856,7 +1852,7 @@ func GetAllActorArchiveDB(db *sql.DB, id string, offset int) Collection {
CheckError(err, "error query object from db")
defer rows.Close()
- for rows.Next(){
+ for rows.Next() {
var post ObjectBase
err = rows.Scan(&post.Id, &post.Updated)
@@ -1884,7 +1880,7 @@ func GetActorCollectionDBType(db *sql.DB, actorId string, nType string) Collecti
CheckError(err, "error query object from db archive")
defer rows.Close()
- for rows.Next(){
+ for rows.Next() {
var post ObjectBase
var actor Actor
var attachID string
@@ -1924,7 +1920,7 @@ func GetActorCollectionDBTypeLimit(db *sql.DB, actorId string, nType string, lim
CheckError(err, "error query object from db archive")
defer rows.Close()
- for rows.Next(){
+ for rows.Next() {
var post ObjectBase
var actor Actor
var attachID string
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..892d598
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,26 @@
+version: '3'
+services:
+ postgres:
+ image: postgres:13.4-alpine
+ restart: unless-stopped
+ environment:
+ POSTGRES_USER: fchan
+ POSTGRES_PASSWORD: hackme
+ POSTGRES_DB: fchan
+ volumes:
+ - ./pgdata:/var/lib/postgresql/data
+ redis:
+ image: redis:6.2-alpine
+ restart: unless-stopped
+ fchan:
+ build: ./
+ restart: unless-stopped
+ volumes:
+ - ./config:/app/config
+ - ./public/:/app/public/
+ - ./pem/:/app/pem/
+ ports:
+ - "3000:3000"
+ links:
+ - redis
+ - postgres
diff --git a/follow.go b/follow.go
index b4431dc..49558cd 100644
--- a/follow.go
+++ b/follow.go
@@ -34,7 +34,6 @@ func GetActorFollowers(w http.ResponseWriter, db *sql.DB, id string) {
w.Write(enc)
}
-
func GetActorFollowingDB(db *sql.DB, id string) []ObjectBase {
var followingCollection []ObjectBase
query := `select following from following where id=$1`
@@ -164,7 +163,7 @@ func IsAlreadyFollowing(db *sql.DB, actor string, follow string) bool {
}
}
- return false;
+ return false
}
func IsAlreadyFollower(db *sql.DB, actor string, follow string) bool {
@@ -176,7 +175,7 @@ func IsAlreadyFollower(db *sql.DB, actor string, follow string) bool {
}
}
- return false;
+ return false
}
func SetActorFollowerDB(db *sql.DB, activity Activity) Activity {
@@ -202,7 +201,7 @@ func SetActorFollowerDB(db *sql.DB, activity Activity) Activity {
activity.Type = "Accept"
return activity
} else {
- query = `insert into follower (id, follower) values ($1, $2)`
+ query = `insert into follower (id, follower) values ($1, $2)`
activity.Summary = activity.Object.Actor + " Follow " + activity.Actor.Id
_, err := db.Exec(query, activity.Actor.Id, activity.Object.Actor)
@@ -215,9 +214,6 @@ func SetActorFollowerDB(db *sql.DB, activity Activity) Activity {
activity.Type = "Accept"
return activity
}
-
-
- return activity
}
func SetActorFollowingDB(db *sql.DB, activity Activity) Activity {
@@ -283,8 +279,7 @@ func SetActorFollowingDB(db *sql.DB, activity Activity) Activity {
return activity
}
-
- return activity
+ return activity
}
func AutoFollow(db *sql.DB, actor string) {
diff --git a/main.go b/main.go
index de58247..68d8a4f 100644
--- a/main.go
+++ b/main.go
@@ -1,49 +1,52 @@
package main
import (
- "fmt"
- "strings"
- "strconv"
- "net/http"
- "net/url"
+ "bufio"
+ "bytes"
+ "crypto/sha256"
"database/sql"
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "github.com/gofrs/uuid"
_ "github.com/lib/pq"
- "math/rand"
"html/template"
- "time"
- "regexp"
- "os/exec"
- "bytes"
- "encoding/json"
+ "io"
"io/ioutil"
+ "log"
+ "math/rand"
"mime/multipart"
+ "net/http"
+ "net/url"
"os"
- "bufio"
- "io"
- "github.com/gofrs/uuid"
- "crypto/sha256"
- "encoding/hex"
+ "os/exec"
+ "path"
+ "regexp"
+ "strconv"
+ "strings"
+ "time"
)
-var Port = ":" + GetConfigValue("instanceport")
-var TP = GetConfigValue("instancetp")
-var Instance = GetConfigValue("instance")
+var Port = ":" + GetConfigValue("instanceport", "3000")
+var TP = GetConfigValue("instancetp", "")
+var Instance = GetConfigValue("instance", "")
var Domain = TP + "" + Instance
+var TorInstance = IsOnion(Instance)
-var authReq = []string{"captcha","email","passphrase"}
+var authReq = []string{"captcha", "email", "passphrase"}
-var supportedFiles = []string{"image/gif","image/jpeg","image/png", "image/webp", "image/apng","video/mp4","video/ogg","video/webm","audio/mpeg","audio/ogg","audio/wav", "audio/wave", "audio/x-wav"}
+var supportedFiles = []string{"image/gif", "image/jpeg", "image/png", "image/webp", "image/apng", "video/mp4", "video/ogg", "video/webm", "audio/mpeg", "audio/ogg", "audio/wav", "audio/wave", "audio/x-wav"}
-var SiteEmail = GetConfigValue("emailaddress") //contact@fchan.xyz
-var SiteEmailPassword = GetConfigValue("emailpass")
-var SiteEmailServer = GetConfigValue("emailserver") //mail.fchan.xyz
-var SiteEmailPort = GetConfigValue("emailport") //587
+var SiteEmail = GetConfigValue("emailaddress", "") //contact@fchan.xyz
+var SiteEmailPassword = GetConfigValue("emailpass", "")
+var SiteEmailServer = GetConfigValue("emailserver", "") //mail.fchan.xyz
+var SiteEmailPort = GetConfigValue("emailport", "") //587
-var TorProxy = GetConfigValue("torproxy") //127.0.0.1:9050
+var TorProxy = GetConfigValue("torproxy", "") //127.0.0.1:9050
-var PublicIndexing = strings.ToLower(GetConfigValue("publicindex"))
+var PublicIndexing = strings.ToLower(GetConfigValue("publicindex", "false"))
-var Salt = GetConfigValue("instancesalt")
+var Salt = GetConfigValue("instancesalt", "")
var activitystreams = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
@@ -51,13 +54,15 @@ var MediaHashs = make(map[string]string)
var ActorCache = make(map[string]Actor)
+var Themes []string
+
func main() {
CreatedNeededDirectories()
InitCache()
- db := ConnectDB();
+ db := ConnectDB()
defer db.Close()
@@ -77,13 +82,29 @@ func main() {
// root actor is used to follow remote feeds that are not local
//name, prefname, summary, auth requirements, restricted
- if GetConfigValue("instancename") != "" {
- CreateNewBoardDB(db, *CreateNewActor("", GetConfigValue("instancename"), GetConfigValue("instancesummary"), authReq, false))
+ if GetConfigValue("instancename", "") != "" {
+ CreateNewBoardDB(db, *CreateNewActor("", GetConfigValue("instancename", ""), GetConfigValue("instancesummary", ""), authReq, false))
if PublicIndexing == "true" {
AddInstanceToIndex(Domain)
}
}
+ // get list of themes
+ themes, err := ioutil.ReadDir("./static/css/themes")
+ if err != nil {
+ panic(err)
+ }
+
+ for _, f := range themes {
+ if f.Name() == "default.css" {
+ continue
+ }
+
+ if e := path.Ext(f.Name()); e == ".css" {
+ Themes = append(Themes, strings.TrimSuffix(f.Name(), e))
+ }
+ }
+
// Allow access to public media folder
fileServer := http.FileServer(http.Dir("./public"))
http.Handle("/public/", http.StripPrefix("/public", neuter(fileServer)))
@@ -92,7 +113,7 @@ func main() {
http.Handle("/static/", http.StripPrefix("/static", neuter(javascriptFiles)))
// main routing
- http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
// remove trailing slash
@@ -132,15 +153,15 @@ func main() {
mainFollowing = (path == "/following")
mainFollowers = (path == "/followers")
} else {
- actorMain = (path == "/" + actor.Name)
- actorInbox = (path == "/" + actor.Name + "/inbox")
- actorCatalog = (path == "/" + actor.Name + "/catalog")
- actorOutbox = (path == "/" + actor.Name + "/outbox")
- actorFollowing = (path == "/" + actor.Name + "/following")
- actorFollowers = (path == "/" + actor.Name + "/followers")
- actorReported = (path == "/" + actor.Name + "/reported")
- actorVerification = (path == "/" + actor.Name + "/verification")
- actorArchive = (path == "/" + actor.Name + "/archive")
+ actorMain = (path == "/"+actor.Name)
+ actorInbox = (path == "/"+actor.Name+"/inbox")
+ actorCatalog = (path == "/"+actor.Name+"/catalog")
+ actorOutbox = (path == "/"+actor.Name+"/outbox")
+ actorFollowing = (path == "/"+actor.Name+"/following")
+ actorFollowers = (path == "/"+actor.Name+"/followers")
+ actorReported = (path == "/"+actor.Name+"/reported")
+ actorVerification = (path == "/"+actor.Name+"/verification")
+ actorArchive = (path == "/"+actor.Name+"/archive")
escapedActorName := strings.Replace(actor.Name, "*", "\\*", -1)
escapedActorName = strings.Replace(escapedActorName, "^", "\\^", -1)
@@ -155,7 +176,7 @@ func main() {
re = regexp.MustCompile("/" + escapedActorName + "/\\w+")
- actorPost = re.MatchString(path)
+ actorPost = re.MatchString(path)
}
if mainActor {
@@ -311,10 +332,10 @@ func main() {
w.Write([]byte("404 no path"))
})
- http.HandleFunc("/news/", func(w http.ResponseWriter, r *http.Request){
+ http.HandleFunc("/news/", func(w http.ResponseWriter, r *http.Request) {
timestamp := r.URL.Path[6:]
- if(len(timestamp) < 2) {
+ if len(timestamp) < 2 {
AllNewsGet(w, r, db)
return
}
@@ -332,47 +353,46 @@ func main() {
}
})
- http.HandleFunc("/post", func(w http.ResponseWriter, r *http.Request){
+ http.HandleFunc("/post", func(w http.ResponseWriter, r *http.Request) {
r.ParseMultipartForm(10 << 20)
file, header, _ := r.FormFile("file")
- if(IsPostBlacklist(db, r.FormValue("comment"))){
+ if IsPostBlacklist(db, r.FormValue("comment")) {
fmt.Println("\n\nBlacklist post blocked\n\n")
- http.Redirect(w, r, Domain + "/", http.StatusMovedPermanently)
+ http.Redirect(w, r, Domain+"/", http.StatusMovedPermanently)
return
}
- if(file != nil && header.Size > (7 << 20)){
+ if file != nil && header.Size > (7<<20) {
w.Write([]byte("7MB max file size"))
return
}
- if(r.FormValue("inReplyTo") == "" && file == nil) {
+ if r.FormValue("inReplyTo") == "" && file == nil {
w.Write([]byte("Media is required for new posts"))
return
}
-
- if(r.FormValue("inReplyTo") == "" || file == nil) {
- if(r.FormValue("comment") == "" && r.FormValue("subject") == ""){
+ if r.FormValue("inReplyTo") == "" || file == nil {
+ if r.FormValue("comment") == "" && r.FormValue("subject") == "" {
w.Write([]byte("Comment or Subject required"))
return
}
}
- if(len(r.FormValue("comment")) > 2000) {
+ if len(r.FormValue("comment")) > 2000 {
w.Write([]byte("Comment limit 2000 characters"))
return
}
- if(len(r.FormValue("subject")) > 100 || len(r.FormValue("name")) > 100 || len(r.FormValue("options")) > 100) {
+ if len(r.FormValue("subject")) > 100 || len(r.FormValue("name")) > 100 || len(r.FormValue("options")) > 100 {
w.Write([]byte("Name, Subject or Options limit 100 characters"))
return
}
- if(r.FormValue("captcha") == "") {
+ if r.FormValue("captcha") == "" {
w.Write([]byte("Incorrect Captcha"))
return
}
@@ -380,7 +400,7 @@ func main() {
b := bytes.Buffer{}
we := multipart.NewWriter(&b)
- if(file != nil){
+ if file != nil {
var fw io.Writer
fw, err := we.CreateFormFile("file", header.Filename)
@@ -395,22 +415,22 @@ func main() {
reply := ParseCommentForReply(r.FormValue("comment"))
for key, r0 := range r.Form {
- if(key == "captcha") {
- err := we.WriteField(key, r.FormValue("captchaCode") + ":" + r.FormValue("captcha"))
+ if key == "captcha" {
+ err := we.WriteField(key, r.FormValue("captchaCode")+":"+r.FormValue("captcha"))
CheckError(err, "error with writing captcha field")
- }else if(key == "name") {
+ } else if key == "name" {
name, tripcode := CreateNameTripCode(r, db)
err := we.WriteField(key, name)
CheckError(err, "error with writing name field")
err = we.WriteField("tripcode", tripcode)
CheckError(err, "error with writing tripcode field")
- }else{
+ } else {
err := we.WriteField(key, r0[0])
CheckError(err, "error with writing field")
}
}
- if(r.FormValue("inReplyTo") == "" && reply != ""){
+ if r.FormValue("inReplyTo") == "" && reply != "" {
err := we.WriteField("inReplyTo", reply)
CheckError(err, "error with writing inReplyTo field")
}
@@ -430,7 +450,7 @@ func main() {
defer resp.Body.Close()
- if(resp.StatusCode == 200){
+ if resp.StatusCode == 200 {
body, _ := ioutil.ReadAll(resp.Body)
@@ -438,32 +458,32 @@ func main() {
obj = ParseOptions(r, obj)
for _, e := range obj.Option {
- if(e == "noko" || e == "nokosage"){
- http.Redirect(w, r, Domain + "/" + r.FormValue("boardName") + "/" + shortURL(r.FormValue("sendTo"), string(body)) , http.StatusMovedPermanently)
+ if e == "noko" || e == "nokosage" {
+ http.Redirect(w, r, Domain+"/"+r.FormValue("boardName")+"/"+shortURL(r.FormValue("sendTo"), string(body)), http.StatusMovedPermanently)
return
}
}
- if(r.FormValue("returnTo") == "catalog"){
- http.Redirect(w, r, Domain + "/" + r.FormValue("boardName") + "/catalog", http.StatusMovedPermanently)
+ if r.FormValue("returnTo") == "catalog" {
+ http.Redirect(w, r, Domain+"/"+r.FormValue("boardName")+"/catalog", http.StatusMovedPermanently)
} else {
- http.Redirect(w, r, Domain + "/" + r.FormValue("boardName"), http.StatusMovedPermanently)
+ http.Redirect(w, r, Domain+"/"+r.FormValue("boardName"), http.StatusMovedPermanently)
}
return
}
- if(resp.StatusCode == 403){
+ if resp.StatusCode == 403 {
w.Write([]byte("Incorrect Captcha"))
return
}
- http.Redirect(w, r, Domain + "/" + r.FormValue("boardName"), http.StatusMovedPermanently)
+ http.Redirect(w, r, Domain+"/"+r.FormValue("boardName"), http.StatusMovedPermanently)
})
- http.HandleFunc("/" + *Key + "/", func(w http.ResponseWriter, r *http.Request) {
+ http.HandleFunc("/"+*Key+"/", func(w http.ResponseWriter, r *http.Request) {
id, _ := GetPasswordFromSession(r)
- actor := GetActorFromPath(db, r.URL.Path, "/" + *Key + "/")
+ actor := GetActorFromPath(db, r.URL.Path, "/"+*Key+"/")
if actor.Id == "" {
actor = GetActorFromDB(db, Domain)
@@ -481,10 +501,10 @@ func main() {
re = regexp.MustCompile("/" + *Key + "/" + actor.Name)
manage := re.MatchString(r.URL.Path)
- re = regexp.MustCompile("/" + *Key )
+ re = regexp.MustCompile("/" + *Key)
admin := re.MatchString(r.URL.Path)
- re = regexp.MustCompile("/" + *Key + "/follow" )
+ re = regexp.MustCompile("/" + *Key + "/follow")
adminFollow := re.MatchString(r.URL.Path)
if follow || adminFollow {
@@ -517,7 +537,7 @@ func main() {
}
//follow all of boards followers
- } else if followers.MatchString(follow){
+ } else if followers.MatchString(follow) {
followersActor := FingerActor(follow)
col := GetActorCollection(followersActor.Followers)
@@ -544,28 +564,28 @@ func main() {
return
}
- if FingerActor(follow).Id != ""{
+ if FingerActor(follow).Id != "" {
MakeActivityRequestOutbox(db, followActivity)
}
}
var redirect string
- if(actor.Name != "main") {
+ if actor.Name != "main" {
redirect = "/" + actor.Name
}
- http.Redirect(w, r, "/" + *Key + "/" + redirect, http.StatusSeeOther)
+ http.Redirect(w, r, "/"+*Key+"/"+redirect, http.StatusSeeOther)
} else if manage && actor.Name != "" {
t := template.Must(template.New("").Funcs(template.FuncMap{
- "sub": func (i, j int) int { return i - j }}).ParseFiles("./static/main.html", "./static/manage.html"))
+ "sub": func(i, j int) int { return i - j }}).ParseFiles("./static/main.html", "./static/manage.html"))
follow := GetActorCollection(actor.Following)
follower := GetActorCollection(actor.Followers)
- reported := GetActorCollectionReq(r, actor.Id + "/reported")
+ reported := GetActorCollectionReq(r, actor.Id+"/reported")
var following []string
var followers []string
- var reports []Report
+ var reports []Report
for _, e := range follow.Items {
following = append(following, e.Id)
@@ -578,7 +598,7 @@ func main() {
for _, e := range reported.Items {
var r Report
r.Count = int(e.Size)
- r.ID = e.Id
+ r.ID = e.Id
r.Reason = e.Content
reports = append(reports, r)
}
@@ -588,7 +608,7 @@ func main() {
for _, e := range localReports {
var r Report
r.Count = e.Count
- r.ID = e.ID
+ r.ID = e.ID
r.Reason = e.Reason
reports = append(reports, r)
}
@@ -596,7 +616,7 @@ func main() {
var adminData AdminPage
adminData.Following = following
adminData.Followers = followers
- adminData.Reported = reports
+ adminData.Reported = reports
adminData.Domain = Domain
adminData.IsLocal = IsActorLocal(db, actor.Id)
@@ -609,14 +629,19 @@ func main() {
adminData.Board.Post.Actor = actor.Id
- adminData.AutoSubscribe = GetActorAutoSubscribeDB(db, actor.Id);
+ adminData.AutoSubscribe = GetActorAutoSubscribeDB(db, actor.Id)
+ adminData.Themes = &Themes
- t.ExecuteTemplate(w, "layout", adminData)
+ err := t.ExecuteTemplate(w, "layout", adminData)
+ if err != nil {
+ // TODO: actual error handling
+ log.Printf("mod page: %s\n", err)
+ }
} else if admin || actor.Id == Domain {
t := template.Must(template.New("").Funcs(template.FuncMap{
- "sub": func (i, j int) int { return i - j }}).ParseFiles("./static/main.html", "./static/nadmin.html"))
+ "sub": func(i, j int) int { return i - j }}).ParseFiles("./static/main.html", "./static/nadmin.html"))
actor := GetActor(Domain)
follow := GetActorCollection(actor.Following).Items
@@ -639,7 +664,7 @@ func main() {
adminData.Actor = actor.Id
adminData.Key = *Key
adminData.Domain = Domain
- adminData.Board.ModCred,_ = GetPasswordFromSession(r)
+ adminData.Board.ModCred, _ = GetPasswordFromSession(r)
adminData.Boards = Boards
@@ -647,11 +672,17 @@ func main() {
adminData.PostBlacklist = GetRegexBlacklistDB(db)
- t.ExecuteTemplate(w, "layout", adminData)
+ adminData.Themes = &Themes
+
+ err := t.ExecuteTemplate(w, "layout", adminData)
+ if err != nil {
+ // TODO: actual error handling
+ log.Printf("mod page: %s\n", err)
+ }
}
})
- http.HandleFunc("/" + *Key + "/addboard", func(w http.ResponseWriter, r *http.Request) {
+ http.HandleFunc("/"+*Key+"/addboard", func(w http.ResponseWriter, r *http.Request) {
actor := GetActorFromDB(db, Domain)
@@ -688,10 +719,10 @@ func main() {
newActorActivity.Object.Sensitive = board.Restricted
MakeActivityRequestOutbox(db, newActorActivity)
- http.Redirect(w, r, "/" + *Key, http.StatusSeeOther)
+ http.Redirect(w, r, "/"+*Key, http.StatusSeeOther)
})
- http.HandleFunc("/" + *Key + "/postnews", func(w http.ResponseWriter, r *http.Request) {
+ http.HandleFunc("/"+*Key+"/postnews", func(w http.ResponseWriter, r *http.Request) {
actor := GetActorFromDB(db, Domain)
@@ -709,7 +740,7 @@ func main() {
http.Redirect(w, r, "/", http.StatusSeeOther)
})
- http.HandleFunc("/" + *Key + "/newsdelete/", func(w http.ResponseWriter, r *http.Request){
+ http.HandleFunc("/"+*Key+"/newsdelete/", func(w http.ResponseWriter, r *http.Request) {
actor := GetActorFromDB(db, Domain)
@@ -721,7 +752,7 @@ func main() {
tsint, err := strconv.Atoi(timestamp)
- if(err != nil){
+ if err != nil {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte("404 no path"))
return
@@ -731,8 +762,8 @@ func main() {
}
})
- http.HandleFunc("/verify", func(w http.ResponseWriter, r *http.Request){
- if(r.Method == "POST") {
+ http.HandleFunc("/verify", func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == "POST" {
r.ParseForm()
identifier := r.FormValue("id")
code := r.FormValue("code")
@@ -743,7 +774,7 @@ func main() {
j, _ := json.Marshal(&verify)
- req, err := http.NewRequest("POST", Domain + "/auth", bytes.NewBuffer(j))
+ req, err := http.NewRequest("POST", Domain+"/auth", bytes.NewBuffer(j))
CheckError(err, "error making verify req")
@@ -759,14 +790,14 @@ func main() {
body := string(rBody)
- if(resp.StatusCode != 200) {
+ if resp.StatusCode != 200 {
t := template.Must(template.ParseFiles("./static/verify.html"))
- t.Execute(w, "wrong password " + verify.Code)
+ t.Execute(w, "wrong password "+verify.Code)
} else {
sessionToken, _ := uuid.NewV4()
- _, err := cache.Do("SETEX", sessionToken, "86400", body + "|" + verify.Code)
+ _, err := cache.Do("SETEX", sessionToken, "86400", body+"|"+verify.Code)
if err != nil {
t := template.Must(template.ParseFiles("./static/verify.html"))
t.Execute(w, "")
@@ -853,22 +884,22 @@ func main() {
TombstoneObjectAndReplies(db, id)
}
- if IsIDLocal(db, id){
+ if IsIDLocal(db, id) {
go DeleteObjectRequest(db, id)
}
UnArchiveLast(db, actor)
if !isOP {
- if (!IsIDLocal(db, id)){
- http.Redirect(w, r, "/" + board + "/" + remoteShort(OP), http.StatusSeeOther)
+ if !IsIDLocal(db, id) {
+ http.Redirect(w, r, "/"+board+"/"+remoteShort(OP), http.StatusSeeOther)
return
} else {
http.Redirect(w, r, OP, http.StatusSeeOther)
return
}
} else {
- http.Redirect(w, r, "/" + board, http.StatusSeeOther)
+ http.Redirect(w, r, "/"+board, http.StatusSeeOther)
return
}
}
@@ -877,7 +908,7 @@ func main() {
w.Write([]byte(""))
})
- http.HandleFunc("/delete", func(w http.ResponseWriter, r *http.Request){
+ http.HandleFunc("/delete", func(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
board := r.URL.Query().Get("board")
@@ -907,12 +938,11 @@ func main() {
UnArchiveLast(db, actor.Id)
-
- if(manage == "t"){
- http.Redirect(w, r, "/" + *Key + "/" + board , http.StatusSeeOther)
+ if manage == "t" {
+ http.Redirect(w, r, "/"+*Key+"/"+board, http.StatusSeeOther)
return
} else {
- http.Redirect(w, r, "/" + board, http.StatusSeeOther)
+ http.Redirect(w, r, "/"+board, http.StatusSeeOther)
return
}
}
@@ -942,33 +972,30 @@ func main() {
TombstoneObjectAndReplies(db, id)
}
- if IsIDLocal(db, id){
+ if IsIDLocal(db, id) {
go DeleteObjectRequest(db, id)
}
UnArchiveLast(db, actor)
- if(manage == "t"){
- http.Redirect(w, r, "/" + *Key + "/" + board , http.StatusSeeOther)
+ if manage == "t" {
+ http.Redirect(w, r, "/"+*Key+"/"+board, http.StatusSeeOther)
return
} else if !isOP {
- if (!IsIDLocal(db, id)){
- http.Redirect(w, r, "/" + board + "/" + remoteShort(OP), http.StatusSeeOther)
+ if !IsIDLocal(db, id) {
+ http.Redirect(w, r, "/"+board+"/"+remoteShort(OP), http.StatusSeeOther)
return
} else {
http.Redirect(w, r, OP, http.StatusSeeOther)
return
}
} else {
- http.Redirect(w, r, "/" + board, http.StatusSeeOther)
+ http.Redirect(w, r, "/"+board, http.StatusSeeOther)
return
}
-
- w.WriteHeader(http.StatusBadRequest)
- w.Write([]byte(""))
})
- http.HandleFunc("/deleteattach", func(w http.ResponseWriter, r *http.Request){
+ http.HandleFunc("/deleteattach", func(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
board := r.URL.Query().Get("board")
@@ -997,11 +1024,11 @@ func main() {
DeletePreviewFromFile(db, id)
TombstonePreviewFromDB(db, id)
- if(manage == "t"){
- http.Redirect(w, r, "/" + *Key + "/" + board , http.StatusSeeOther)
+ if manage == "t" {
+ http.Redirect(w, r, "/"+*Key+"/"+board, http.StatusSeeOther)
return
} else {
- http.Redirect(w, r, "/" + board, http.StatusSeeOther)
+ http.Redirect(w, r, "/"+board, http.StatusSeeOther)
return
}
}
@@ -1009,7 +1036,7 @@ func main() {
actor := col.OrderedItems[0].Actor
var OP string
- if (len(col.OrderedItems[0].InReplyTo) > 0 && col.OrderedItems[0].InReplyTo[0].Id != "") {
+ if len(col.OrderedItems[0].InReplyTo) > 0 && col.OrderedItems[0].InReplyTo[0].Id != "" {
OP = col.OrderedItems[0].InReplyTo[0].Id
} else {
OP = id
@@ -1027,22 +1054,19 @@ func main() {
DeletePreviewFromFile(db, id)
TombstonePreviewFromDB(db, id)
- if (manage == "t") {
- http.Redirect(w, r, "/" + *Key + "/" + board, http.StatusSeeOther)
+ if manage == "t" {
+ http.Redirect(w, r, "/"+*Key+"/"+board, http.StatusSeeOther)
return
} else if !IsIDLocal(db, OP) {
- http.Redirect(w, r, "/" + board + "/" + remoteShort(OP), http.StatusSeeOther)
+ http.Redirect(w, r, "/"+board+"/"+remoteShort(OP), http.StatusSeeOther)
return
} else {
http.Redirect(w, r, OP, http.StatusSeeOther)
return
}
-
- w.WriteHeader(http.StatusBadRequest)
- w.Write([]byte(""))
})
- http.HandleFunc("/marksensitive", func(w http.ResponseWriter, r *http.Request){
+ http.HandleFunc("/marksensitive", func(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
board := r.URL.Query().Get("board")
@@ -1066,14 +1090,14 @@ func main() {
MarkObjectSensitive(db, id, true)
- http.Redirect(w, r, "/" + board, http.StatusSeeOther)
+ http.Redirect(w, r, "/"+board, http.StatusSeeOther)
return
}
actor := col.OrderedItems[0].Actor
var OP string
- if (len(col.OrderedItems[0].InReplyTo) > 0 && col.OrderedItems[0].InReplyTo[0].Id != "") {
+ if len(col.OrderedItems[0].InReplyTo) > 0 && col.OrderedItems[0].InReplyTo[0].Id != "" {
OP = col.OrderedItems[0].InReplyTo[0].Id
} else {
OP = id
@@ -1088,18 +1112,15 @@ func main() {
MarkObjectSensitive(db, id, true)
if !IsIDLocal(db, OP) {
- http.Redirect(w, r, "/" + board + "/" + remoteShort(OP), http.StatusSeeOther)
+ http.Redirect(w, r, "/"+board+"/"+remoteShort(OP), http.StatusSeeOther)
return
} else {
http.Redirect(w, r, OP, http.StatusSeeOther)
return
}
-
- w.WriteHeader(http.StatusBadRequest)
- w.Write([]byte(""))
})
- http.HandleFunc("/remove", func(w http.ResponseWriter, r *http.Request){
+ http.HandleFunc("/remove", func(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
manage := r.URL.Query().Get("manage")
board := r.URL.Query().Get("board")
@@ -1136,27 +1157,24 @@ func main() {
SetObjectAndReplies(db, id, "Removed")
}
- if(manage == "t"){
- http.Redirect(w, r, "/" + *Key + "/" + board , http.StatusSeeOther)
+ if manage == "t" {
+ http.Redirect(w, r, "/"+*Key+"/"+board, http.StatusSeeOther)
return
} else if !isOP {
- if (!IsIDLocal(db, id)){
- http.Redirect(w, r, "/" + board + "/" + remoteShort(OP), http.StatusSeeOther)
+ if !IsIDLocal(db, id) {
+ http.Redirect(w, r, "/"+board+"/"+remoteShort(OP), http.StatusSeeOther)
return
} else {
http.Redirect(w, r, OP, http.StatusSeeOther)
return
}
} else {
- http.Redirect(w, r, "/" + board, http.StatusSeeOther)
+ http.Redirect(w, r, "/"+board, http.StatusSeeOther)
return
}
-
- w.WriteHeader(http.StatusBadRequest)
- w.Write([]byte(""))
})
- http.HandleFunc("/removeattach", func(w http.ResponseWriter, r *http.Request){
+ http.HandleFunc("/removeattach", func(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
manage := r.URL.Query().Get("manage")
@@ -1165,7 +1183,7 @@ func main() {
actor := col.OrderedItems[0].Actor
var OP string
- if (len(col.OrderedItems[0].InReplyTo) > 0 && col.OrderedItems[0].InReplyTo[0].Id != "") {
+ if len(col.OrderedItems[0].InReplyTo) > 0 && col.OrderedItems[0].InReplyTo[0].Id != "" {
OP = col.OrderedItems[0].InReplyTo[0].Id
} else {
OP = id
@@ -1188,22 +1206,19 @@ func main() {
SetAttachmentFromDB(db, id, "Removed")
SetPreviewFromDB(db, id, "Removed")
- if (manage == "t") {
- http.Redirect(w, r, "/" + *Key + "/" + board, http.StatusSeeOther)
+ if manage == "t" {
+ http.Redirect(w, r, "/"+*Key+"/"+board, http.StatusSeeOther)
return
} else if !IsIDLocal(db, OP) {
- http.Redirect(w, r, "/" + board + "/" + remoteShort(OP), http.StatusSeeOther)
+ http.Redirect(w, r, "/"+board+"/"+remoteShort(OP), http.StatusSeeOther)
return
} else {
http.Redirect(w, r, OP, http.StatusSeeOther)
return
}
-
- w.WriteHeader(http.StatusBadRequest)
- w.Write([]byte(""))
})
- http.HandleFunc("/report", func(w http.ResponseWriter, r *http.Request){
+ http.HandleFunc("/report", func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
@@ -1222,7 +1237,7 @@ func main() {
return
}
- if(close != "1" && !CheckCaptcha(db, captcha)) {
+ if close != "1" && !CheckCaptcha(db, captcha) {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("captcha required"))
return
@@ -1237,13 +1252,13 @@ func main() {
if !IsIDLocal(db, id) {
CloseLocalReportDB(db, id, board)
- http.Redirect(w, r, "/" + *Key + "/" + board, http.StatusSeeOther)
+ http.Redirect(w, r, "/"+*Key+"/"+board, http.StatusSeeOther)
return
}
reported := DeleteReportActivity(db, id)
if reported {
- http.Redirect(w, r, "/" + *Key + "/" + board, http.StatusSeeOther)
+ http.Redirect(w, r, "/"+*Key+"/"+board, http.StatusSeeOther)
return
}
@@ -1254,7 +1269,7 @@ func main() {
if !IsIDLocal(db, id) {
CreateLocalReportDB(db, id, board, reason)
- http.Redirect(w, r, "/" + board + "/" + remoteShort(id), http.StatusSeeOther)
+ http.Redirect(w, r, "/"+board+"/"+remoteShort(id), http.StatusSeeOther)
return
}
@@ -1268,7 +1283,7 @@ func main() {
w.Write([]byte(""))
})
- http.HandleFunc("/auth", func(w http.ResponseWriter, r *http.Request){
+ http.HandleFunc("/auth", func(w http.ResponseWriter, r *http.Request) {
var verify Verify
defer r.Body.Close()
@@ -1292,7 +1307,7 @@ func main() {
http.HandleFunc("/.well-known/webfinger", func(w http.ResponseWriter, r *http.Request) {
acct := r.URL.Query()["resource"]
- if(len(acct) < 1) {
+ if len(acct) < 1 {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("resource needs a value"))
return
@@ -1302,7 +1317,7 @@ func main() {
actorDomain := strings.Split(acct[0], "@")
- if(len(actorDomain) < 2) {
+ if len(actorDomain) < 2 {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("accpets only subject form of acct:board@instance"))
return
@@ -1314,7 +1329,7 @@ func main() {
actorDomain[0] = "/" + actorDomain[0]
}
- if !IsActorLocal(db, TP + "" + actorDomain[1] + "" + actorDomain[0]) {
+ if !IsActorLocal(db, TP+""+actorDomain[1]+""+actorDomain[0]) {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("actor not local"))
return
@@ -1360,7 +1375,7 @@ func main() {
SetObjectType(db, id, "Note")
- http.Redirect(w, r, "/" + board + "/archive", http.StatusSeeOther)
+ http.Redirect(w, r, "/"+board+"/archive", http.StatusSeeOther)
})
http.HandleFunc("/blacklist", func(w http.ResponseWriter, r *http.Request) {
@@ -1399,7 +1414,7 @@ func main() {
}
}
- http.Redirect(w, r, "/" + *Key + "#regex", http.StatusSeeOther)
+ http.Redirect(w, r, "/"+*Key+"#regex", http.StatusSeeOther)
})
http.HandleFunc("/api/media", func(w http.ResponseWriter, r *http.Request) {
@@ -1424,7 +1439,7 @@ func main() {
AutoFollow(db, actor.Id)
}
- http.Redirect(w, r, "/" + *Key + "/" + board, http.StatusSeeOther)
+ http.Redirect(w, r, "/"+*Key+"/"+board, http.StatusSeeOther)
})
fmt.Println("Server for " + Domain + " running on port " + Port)
@@ -1435,7 +1450,7 @@ func main() {
http.ListenAndServe(Port, nil)
}
-func CheckError(e error, m string) error{
+func CheckError(e error, m string) error {
if e != nil {
fmt.Println()
fmt.Println(m)
@@ -1448,13 +1463,13 @@ func CheckError(e error, m string) error{
func ConnectDB() *sql.DB {
- host := GetConfigValue("dbhost")
- port,_ := strconv.Atoi(GetConfigValue("dbport"))
- user := GetConfigValue("dbuser")
- password := GetConfigValue("dbpass")
- dbname := GetConfigValue("dbname")
+ host := GetConfigValue("dbhost", "localhost")
+ port, _ := strconv.Atoi(GetConfigValue("dbport", "5432"))
+ user := GetConfigValue("dbuser", "postgres")
+ password := GetConfigValue("dbpass", "password")
+ dbname := GetConfigValue("dbname", "server")
- psqlInfo := fmt.Sprintf("host=%s port=%d user=%s password=%s " +
+ psqlInfo := fmt.Sprintf("host=%s port=%d user=%s password=%s "+
"dbname=%s sslmode=disable", host, port, user, password, dbname)
db, err := sql.Open("postgres", psqlInfo)
@@ -1478,14 +1493,14 @@ func CreateKey(len int) string {
}
func neuter(next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if strings.HasSuffix(r.URL.Path, "/") {
- http.NotFound(w, r)
- return
- }
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if strings.HasSuffix(r.URL.Path, "/") {
+ http.NotFound(w, r)
+ return
+ }
- next.ServeHTTP(w, r)
- })
+ next.ServeHTTP(w, r)
+ })
}
func CreateTripCode(input string) string {
@@ -1509,7 +1524,7 @@ func GetActorFromPath(db *sql.DB, location string, prefix string) Actor {
var actor string
- if(len(match) < 1 ) {
+ if len(match) < 1 {
actor = "/"
} else {
actor = strings.Replace(match[1], "/", "", -1)
@@ -1521,10 +1536,10 @@ func GetActorFromPath(db *sql.DB, location string, prefix string) Actor {
var nActor Actor
- nActor = GetActorByNameFromDB(db, actor)
+ nActor = GetActorByNameFromDB(db, actor)
if nActor.Id == "" {
- nActor = GetActorByName(db, actor)
+ nActor = GetActorByName(db, actor)
}
return nActor
@@ -1566,7 +1581,7 @@ func CreateUniqueID(db *sql.DB, actor string) string {
defer rows.Close()
var count int = 0
- for rows.Next(){
+ for rows.Next() {
count += 1
}
@@ -1578,7 +1593,7 @@ func CreateUniqueID(db *sql.DB, actor string) string {
return newID
}
-func CreateNewActor(board string, prefName string, summary string, authReq []string, restricted bool) *Actor{
+func CreateNewActor(board string, prefName string, summary string, authReq []string, restricted bool) *Actor {
actor := new(Actor)
var path string
@@ -1612,7 +1627,7 @@ func GetActorInfo(w http.ResponseWriter, db *sql.DB, id string) {
}
func GetActorPost(w http.ResponseWriter, db *sql.DB, path string) {
- collection := GetCollectionFromPath(db, Domain + "" + path)
+ collection := GetCollectionFromPath(db, Domain+""+path)
if len(collection.OrderedItems) > 0 {
enc, _ := json.MarshalIndent(collection, "", "\t")
w.Header().Set("Content-Type", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"")
@@ -1624,13 +1639,13 @@ func CreateObject(objType string) ObjectBase {
var nObj ObjectBase
nObj.Type = objType
- nObj.Published = time.Now().UTC().Format(time.RFC3339)
- nObj.Updated = time.Now().UTC().Format(time.RFC3339)
+ nObj.Published = time.Now().UTC()
+ nObj.Updated = time.Now().UTC()
return nObj
}
-func AddFollowersToActivity(db *sql.DB, activity Activity) Activity{
+func AddFollowersToActivity(db *sql.DB, activity Activity) Activity {
activity.To = append(activity.To, activity.Actor.Id)
@@ -1733,11 +1748,11 @@ func CreatePreviewObject(obj ObjectBase) *NestedObjectBase {
objFile := re.FindString(obj.Href)
- cmd := exec.Command("convert", "." + objFile ,"-resize", "250x250>", "-strip","." + href)
+ cmd := exec.Command("convert", "."+objFile, "-resize", "250x250>", "-strip", "."+href)
err := cmd.Run()
- if CheckError(err, "error with resize attachment preview") != nil {
+ if CheckError(err, "error with resize attachment preview") != nil {
var preview NestedObjectBase
return &preview
}
@@ -1754,7 +1769,7 @@ func CreateAttachmentObject(file multipart.File, header *multipart.FileHeader) (
fileType := re.ReplaceAllString(contentType, "")
- tempFile, _ := ioutil.TempFile("./public", "*." + fileType)
+ tempFile, _ := ioutil.TempFile("./public", "*."+fileType)
var nAttachment []ObjectBase
var image ObjectBase
@@ -1764,7 +1779,7 @@ func CreateAttachmentObject(file multipart.File, header *multipart.FileHeader) (
image.Href = Domain + "/" + tempFile.Name()
image.MediaType = contentType
image.Size = size
- image.Published = time.Now().UTC().Format(time.RFC3339)
+ image.Published = time.Now().UTC()
nAttachment = append(nAttachment, image)
@@ -1778,25 +1793,25 @@ func ParseCommentForReplies(db *sql.DB, comment string, op string) []ObjectBase
var links []string
- for i:= 0; i < len(match); i++ {
+ for i := 0; i < len(match); i++ {
str := strings.Replace(match[i][0], ">>", "", 1)
str = strings.Replace(str, "www.", "", 1)
str = strings.Replace(str, "http://", "", 1)
str = strings.Replace(str, "https://", "", 1)
str = TP + "" + str
- _ , isReply := IsReplyToOP(db, op, str)
- if !IsInStringArray(links, str) && isReply {
+ _, isReply := IsReplyToOP(db, op, str)
+ if !IsInStringArray(links, str) && isReply {
links = append(links, str)
}
}
var validLinks []ObjectBase
- for i:= 0; i < len(links); i++ {
+ for i := 0; i < len(links); i++ {
_, isValid := CheckValidActivity(links[i])
- if(isValid) {
+ if isValid {
var reply = new(ObjectBase)
reply.Id = links[i]
- reply.Published = time.Now().UTC().Format(time.RFC3339)
+ reply.Published = time.Now().UTC()
validLinks = append(validLinks, *reply)
}
}
@@ -1837,11 +1852,11 @@ func CheckValidActivity(id string) (Collection, bool) {
panic(err)
}
- if respCollection.AtContext.Context == "https://www.w3.org/ns/activitystreams" && respCollection.OrderedItems[0].Id != "" {
- return respCollection, true;
+ if respCollection.AtContext.Context == "https://www.w3.org/ns/activitystreams" && respCollection.OrderedItems[0].Id != "" {
+ return respCollection, true
}
- return respCollection, false;
+ return respCollection, false
}
func GetActor(id string) Actor {
@@ -1854,8 +1869,8 @@ func GetActor(id string) Actor {
actor, instance := GetActorInstance(id)
- if ActorCache[actor + "@" + instance].Id != "" {
- respActor = ActorCache[actor + "@" + instance]
+ if ActorCache[actor+"@"+instance].Id != "" {
+ respActor = ActorCache[actor+"@"+instance]
} else {
req, err := http.NewRequest("GET", id, nil)
@@ -1879,7 +1894,7 @@ func GetActor(id string) Actor {
return respActor
}
- ActorCache[actor + "@" + instance] = respActor
+ ActorCache[actor+"@"+instance] = respActor
}
return respActor
@@ -1894,7 +1909,7 @@ func GetActorCollection(collection string) Collection {
req, err := http.NewRequest("GET", collection, nil)
- CheckError(err, "error with getting actor collection req " + collection)
+ CheckError(err, "error with getting actor collection req "+collection)
req.Header.Set("Accept", activitystreams)
@@ -1913,7 +1928,7 @@ func GetActorCollection(collection string) Collection {
if len(body) > 0 {
err = json.Unmarshal(body, &nCollection)
- CheckError(err, "error getting actor collection from body " + collection)
+ CheckError(err, "error getting actor collection from body "+collection)
}
}
@@ -1925,10 +1940,10 @@ func IsValidActor(id string) (Actor, bool) {
actor := FingerActor(id)
if actor.Id != "" {
- return actor, true;
+ return actor, true
}
- return actor, false;
+ return actor, false
}
func IsActivityLocal(db *sql.DB, activity Activity) bool {
@@ -2161,7 +2176,6 @@ func MakeActivityRequestOutbox(db *sql.DB, activity Activity) {
date := time.Now().UTC().Format(time.RFC1123)
path := strings.Replace(activity.Actor.Outbox, instance, "", 1)
-
path = re.ReplaceAllString(path, "")
sig := fmt.Sprintf("(request-target): %s %s\nhost: %s\ndate: %s", "post", path, instance, date)
@@ -2254,7 +2268,7 @@ func GetCollectionFromID(id string) Collection {
return nColl
}
-func GetConfigValue(value string) string{
+func GetConfigValue(value string, ifnone string) string {
file, err := os.Open("config")
CheckError(err, "there was an error opening the config file")
@@ -2270,10 +2284,10 @@ func GetConfigValue(value string) string{
}
}
- return ""
+ return ifnone
}
-func PrintAdminAuth(db *sql.DB){
+func PrintAdminAuth(db *sql.DB) {
query := fmt.Sprintf("select identifier, code from boardaccess where board='%s' and type='admin'", Domain)
rows, err := db.Query(query)
@@ -2299,14 +2313,14 @@ func IsInStringArray(array []string, value string) bool {
}
func GetUniqueFilename(_type string) string {
- id := RandomID(8)
+ id := RandomID(8)
file := "/public/" + id + "." + _type
for true {
if _, err := os.Stat("." + file); err == nil {
- id = RandomID(8)
+ id = RandomID(8)
file = "/public/" + id + "." + _type
- }else{
+ } else {
return "/public/" + id + "." + _type
}
}
@@ -2374,7 +2388,6 @@ func ResizeAttachmentToPreview(db *sql.DB) {
CheckError(err, "error getting attachments")
-
defer rows.Close()
for rows.Next() {
@@ -2383,7 +2396,7 @@ func ResizeAttachmentToPreview(db *sql.DB) {
var mediatype string
var name string
var size int
- var published string
+ var published time.Time
rows.Scan(&id, &href, &mediatype, &name, &size, &published)
@@ -2417,8 +2430,8 @@ func ResizeAttachmentToPreview(db *sql.DB) {
objFile := re.FindString(href)
- if(id != "") {
- cmd := exec.Command("convert", "." + objFile ,"-resize", "250x250>", "-strip", "." + nHref)
+ if id != "" {
+ cmd := exec.Command("convert", "."+objFile, "-resize", "250x250>", "-strip", "."+nHref)
err := cmd.Run()
@@ -2450,15 +2463,15 @@ func ParseCommentForReply(comment string) string {
var links []string
- for i:= 0; i < len(match); i++ {
+ for i := 0; i < len(match); i++ {
str := strings.Replace(match[i][0], ">>", "", 1)
links = append(links, str)
}
- if(len(links) > 0){
+ if len(links) > 0 {
_, isValid := CheckValidActivity(strings.ReplaceAll(links[0], ">", ""))
- if(isValid) {
+ if isValid {
return links[0]
}
}
@@ -2468,11 +2481,11 @@ func ParseCommentForReply(comment string) string {
func GetActorByName(db *sql.DB, name string) Actor {
var actor Actor
- for _, e := range Boards {
- if e.Actor.Name == name {
- actor = e.Actor
- }
+ for _, e := range Boards {
+ if e.Actor.Name == name {
+ actor = e.Actor
}
+ }
return actor
}
@@ -2482,17 +2495,17 @@ func GetActorCollectionReq(r *http.Request, collection string) Collection {
req, err := http.NewRequest("GET", collection, nil)
- CheckError(err, "error with getting actor collection req " + collection)
+ CheckError(err, "error with getting actor collection req "+collection)
_, pass := GetPasswordFromSession(r)
req.Header.Set("Accept", activitystreams)
- req.Header.Set("Authorization", "Basic " + pass)
+ req.Header.Set("Authorization", "Basic "+pass)
resp, err := RouteProxy(req)
- CheckError(err, "error with getting actor collection resp " + collection)
+ CheckError(err, "error with getting actor collection resp "+collection)
if resp.StatusCode == 200 {
@@ -2502,13 +2515,12 @@ func GetActorCollectionReq(r *http.Request, collection string) Collection {
err = json.Unmarshal(body, &nCollection)
- CheckError(err, "error getting actor collection from body " + collection)
+ CheckError(err, "error getting actor collection from body "+collection)
}
return nCollection
}
-
func shortURL(actorName string, url string) string {
re := regexp.MustCompile(`.+\/`)
@@ -2528,44 +2540,44 @@ func shortURL(actorName string, url string) string {
re = regexp.MustCompile(`\w+$`)
temp := re.ReplaceAllString(op, "")
- if(temp == actor){
+ if temp == actor {
id := localShort(op)
re := regexp.MustCompile(`.+\/`)
replyCheck := re.FindString(reply)
- if(reply != "" && replyCheck == actor){
+ if reply != "" && replyCheck == actor {
id = id + "#" + localShort(reply)
} else if reply != "" {
id = id + "#" + remoteShort(reply)
}
- return id;
- }else{
+ return id
+ } else {
id := remoteShort(op)
re := regexp.MustCompile(`.+\/`)
replyCheck := re.FindString(reply)
- if(reply != "" && replyCheck == actor){
+ if reply != "" && replyCheck == actor {
id = id + "#" + localShort(reply)
} else if reply != "" {
id = id + "#" + remoteShort(reply)
}
- return id;
+ return id
}
}
func localShort(url string) string {
- re := regexp.MustCompile(`\w+$`)
- return re.FindString(StripTransferProtocol(url));
+ re := regexp.MustCompile(`\w+$`)
+ return re.FindString(StripTransferProtocol(url))
}
func remoteShort(url string) string {
re := regexp.MustCompile(`\w+$`)
- id := re.FindString(StripTransferProtocol(url));
+ id := re.FindString(StripTransferProtocol(url))
re = regexp.MustCompile(`.+/.+/`)
@@ -2590,7 +2602,7 @@ func RouteProxy(req *http.Request) (*http.Response, error) {
CheckError(err, "error parsing tor proxy url")
proxyTransport := &http.Transport{Proxy: http.ProxyURL(proxyUrl)}
- client := &http.Client{ Transport: proxyTransport, Timeout: time.Second * 10 }
+ client := &http.Client{Transport: proxyTransport, Timeout: time.Second * 10}
return client.Do(req)
}
@@ -2628,7 +2640,7 @@ func CreatedNeededDirectories() {
}
//looks for actor with pattern of board@instance
-func FingerActor(path string) Actor{
+func FingerActor(path string) Actor {
var nActor Actor
@@ -2638,8 +2650,8 @@ func FingerActor(path string) Actor{
return nActor
}
- if ActorCache[actor + "@" + instance].Id != "" {
- nActor = ActorCache[actor + "@" + instance]
+ if ActorCache[actor+"@"+instance].Id != "" {
+ nActor = ActorCache[actor+"@"+instance]
} else {
r := FingerRequest(actor, instance)
if r != nil && r.StatusCode == 200 {
@@ -2651,16 +2663,16 @@ func FingerActor(path string) Actor{
CheckError(err, "error getting fingerrequet resp from json body")
- ActorCache[actor + "@" + instance] = nActor
+ ActorCache[actor+"@"+instance] = nActor
}
}
return nActor
}
-func FingerRequest(actor string, instance string) (*http.Response){
+func FingerRequest(actor string, instance string) *http.Response {
acct := "acct:" + actor + "@" + instance
- req, err := http.NewRequest("GET", "http://" + instance + "/.well-known/webfinger?resource=" + acct, nil)
+ req, err := http.NewRequest("GET", "http://"+instance+"/.well-known/webfinger?resource="+acct, nil)
CheckError(err, "could not get finger request from id req")
@@ -2682,9 +2694,9 @@ func FingerRequest(actor string, instance string) (*http.Response){
CheckError(err, "error getting fingerrequet resp from json body")
}
- if(len(finger.Links) > 0) {
+ if len(finger.Links) > 0 {
for _, e := range finger.Links {
- if(e.Type == "application/activity+json"){
+ if e.Type == "application/activity+json" {
req, err := http.NewRequest("GET", e.Href, nil)
CheckError(err, "could not get finger request from id req")
@@ -2704,18 +2716,18 @@ func GetActorInstance(path string) (string, string) {
re := regexp.MustCompile(`([@]?([\w\d.-_]+)[@](.+))`)
atFormat := re.MatchString(path)
- if(atFormat) {
+ if atFormat {
match := re.FindStringSubmatch(path)
- if(len(match) > 2) {
+ if len(match) > 2 {
return match[2], match[3]
}
}
re = regexp.MustCompile(`(https?://)(www)?([\w\d-_.:]+)(/|\s+|\r|\r\n)?$`)
mainActor := re.MatchString(path)
- if(mainActor) {
+ if mainActor {
match := re.FindStringSubmatch(path)
- if(len(match) > 2) {
+ if len(match) > 2 {
return "main", match[3]
}
}
@@ -2723,9 +2735,9 @@ func GetActorInstance(path string) (string, string) {
re = regexp.MustCompile(`(https?://)?(www)?([\w\d-_.:]+)\/([\w\d-_.]+)(\/([\w\d-_.]+))?`)
httpFormat := re.MatchString(path)
- if(httpFormat) {
+ if httpFormat {
match := re.FindStringSubmatch(path)
- if(len(match) > 3) {
+ if len(match) > 3 {
if match[4] == "users" {
return match[6], match[3]
}
@@ -2754,7 +2766,7 @@ func AddInstanceToIndex(actor string) {
}
if !alreadyIndex {
- req, err := http.NewRequest("GET", "https://fchan.xyz/addtoindex?id=" + actor, nil)
+ req, err := http.NewRequest("GET", "https://fchan.xyz/addtoindex?id="+actor, nil)
CheckError(err, "error with add instance to actor index req")
@@ -2817,13 +2829,13 @@ func GetCollectionFromReq(path string) Collection {
}
func HashMedia(media string) string {
- h:= sha256.New()
+ h := sha256.New()
h.Write([]byte(media))
return hex.EncodeToString(h.Sum(nil))
}
func HashBytes(media []byte) string {
- h:= sha256.New()
+ h := sha256.New()
h.Write(media)
return hex.EncodeToString(h.Sum(nil))
}
@@ -2876,12 +2888,12 @@ func IsPostBlacklist(db *sql.DB, comment string) bool {
}
func HasValidation(w http.ResponseWriter, r *http.Request, actor Actor) bool {
- id, _ := GetPasswordFromSession(r)
+ id, _ := GetPasswordFromSession(r)
- if id == "" || (id != actor.Id && id != Domain) {
- http.Redirect(w, r, "/", http.StatusSeeOther)
- return false
- }
+ if id == "" || (id != actor.Id && id != Domain) {
+ http.Redirect(w, r, "/", http.StatusSeeOther)
+ return false
+ }
return true
}
diff --git a/outboxGet.go b/outboxGet.go
index f656b3e..d2fabc4 100644
--- a/outboxGet.go
+++ b/outboxGet.go
@@ -35,7 +35,7 @@ func GetCollectionFromPath(db *sql.DB, path string) Collection {
defer rows.Close()
- for rows.Next(){
+ for rows.Next() {
var actor Actor
var post ObjectBase
var attachID string
@@ -72,7 +72,7 @@ func GetCollectionFromPath(db *sql.DB, path string) Collection {
return nColl
}
-func GetObjectFromPath(db *sql.DB, path string) ObjectBase{
+func GetObjectFromPath(db *sql.DB, path string) ObjectBase {
var nObj ObjectBase
diff --git a/outboxPost.go b/outboxPost.go
index 0292e4f..cb48d3e 100644
--- a/outboxPost.go
+++ b/outboxPost.go
@@ -23,18 +23,18 @@ func ParseOutboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) {
defer r.Body.Close()
if contentType == "multipart/form-data" || contentType == "application/x-www-form-urlencoded" {
r.ParseMultipartForm(5 << 20)
- if(BoardHasAuthType(db, actor.Name, "captcha") && CheckCaptcha(db, r.FormValue("captcha"))) {
+ if BoardHasAuthType(db, actor.Name, "captcha") && CheckCaptcha(db, r.FormValue("captcha")) {
f, header, _ := r.FormFile("file")
- if(header != nil) {
+ if header != nil {
defer f.Close()
- if(header.Size > (7 << 20)){
+ if header.Size > (7 << 20) {
w.WriteHeader(http.StatusRequestEntityTooLarge)
w.Write([]byte("7MB max file size"))
return
}
- if(IsMediaBanned(db, f)) {
+ if IsMediaBanned(db, f) {
fmt.Println("media banned")
http.Redirect(w, r, Domain, http.StatusSeeOther)
return
@@ -42,7 +42,7 @@ func ParseOutboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) {
contentType, _ := GetFileContentType(f)
- if(!SupportedMIMEType(contentType)) {
+ if !SupportedMIMEType(contentType) {
w.WriteHeader(http.StatusNotAcceptable)
w.Write([]byte("file type not supported"))
return
@@ -235,7 +235,7 @@ func GetObjectFromJson(obj []byte) ObjectBase {
return nObj
}
-func GetActorFromJson(actor []byte) Actor{
+func GetActorFromJson(actor []byte) Actor {
var generic interface{}
var nActor Actor
err := json.Unmarshal(actor, &generic)
@@ -338,7 +338,7 @@ func ObjectFromForm(r *http.Request, db *sql.DB, obj ObjectBase) ObjectBase {
var tempFile = new(os.File)
obj.Attachment, tempFile = CreateAttachmentObject(file, header)
- defer tempFile.Close();
+ defer tempFile.Close()
fileBytes, _ := ioutil.ReadAll(file)
@@ -348,7 +348,7 @@ func ObjectFromForm(r *http.Request, db *sql.DB, obj ObjectBase) ObjectBase {
if re.MatchString(obj.Attachment[0].MediaType) {
fileLoc := strings.ReplaceAll(obj.Attachment[0].Href, Domain, "")
- cmd := exec.Command("exiv2", "rm", "." + fileLoc)
+ cmd := exec.Command("exiv2", "rm", "."+fileLoc)
err := cmd.Run()
@@ -433,7 +433,7 @@ func ParseOptions(r *http.Request, obj ObjectBase) ObjectBase {
} else if e == "nokosage" {
obj.Option = append(obj.Option, "nokosage")
} else if email.MatchString(e) {
- obj.Option = append(obj.Option, "email:" + e)
+ obj.Option = append(obj.Option, "email:"+e)
} else if wallet.MatchString(e) {
obj.Option = append(obj.Option, "wallet")
var wallet CryptoCur
@@ -502,19 +502,19 @@ func GetActivityFromJson(r *http.Request, db *sql.DB) Activity {
func CheckCaptcha(db *sql.DB, captcha string) bool {
parts := strings.Split(captcha, ":")
- if strings.Trim(parts[0], " ") == "" || strings.Trim(parts[1], " ") == ""{
+ if strings.Trim(parts[0], " ") == "" || strings.Trim(parts[1], " ") == "" {
return false
}
- path := "public/" + parts[0] + ".png"
- code := GetCaptchaCodeDB(db, path)
+ path := "public/" + parts[0] + ".png"
+ code := GetCaptchaCodeDB(db, path)
if code != "" {
DeleteCaptchaCodeDB(db, path)
CreateNewCaptcha(db)
}
- if (code == strings.ToUpper(parts[1])) {
+ if code == strings.ToUpper(parts[1]) {
return true
}
@@ -535,7 +535,7 @@ func ParseInboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) {
return
}
- switch(activity.Type) {
+ switch activity.Type {
case "Create":
for _, e := range activity.To {
if IsActorLocal(db, e) {
@@ -563,7 +563,6 @@ func ParseInboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) {
}
break
-
case "Follow":
for _, e := range activity.To {
if GetActorFromDB(db, e).Id != "" {
diff --git a/session.go b/session.go
index 0867b16..99ab1c2 100644
--- a/session.go
+++ b/session.go
@@ -1,26 +1,25 @@
package main
-
import (
+ "bufio"
"fmt"
- "net/http"
- "bufio"
- "os"
- "strings"
"github.com/gomodule/redigo/redis"
+ "net/http"
+ "os"
+ "strings"
)
var cache redis.Conn
func InitCache() {
- conn, err := redis.DialURL("redis://localhost")
+ conn, err := redis.DialURL(GetConfigValue("redis", "redis://localhost"))
if err != nil {
panic(err)
}
cache = conn
}
-func CheckSession(w http.ResponseWriter, r *http.Request) (interface{}, error){
+func CheckSession(w http.ResponseWriter, r *http.Request) (interface{}, error) {
c, err := r.Cookie("session_token")
@@ -29,15 +28,15 @@ func CheckSession(w http.ResponseWriter, r *http.Request) (interface{}, error){
w.WriteHeader(http.StatusUnauthorized)
return nil, err
}
-
+
w.WriteHeader(http.StatusBadRequest)
return nil, err
}
-
+
sessionToken := c.Value
response, err := cache.Do("GET", sessionToken)
-
+
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return nil, err
@@ -50,7 +49,7 @@ func CheckSession(w http.ResponseWriter, r *http.Request) (interface{}, error){
return response, nil
}
-func GetClientKey() string{
+func GetClientKey() string {
file, err := os.Open("clientkey")
CheckError(err, "could not open client key in file")
diff --git a/static/anews.html b/static/anews.html
index 5ebe1cc..355f310 100644
--- a/static/anews.html
+++ b/static/anews.html
@@ -1,6 +1,6 @@
{{ define "header" }}
<title>{{ .Title }}</title>
-<meta name="description" content="{{ .PreferredUsername }} is a federated image board based on ActivityPub. The current version of the code running on the server is still a work-in-progress product, expect a bumpy ride for the time being. Get the server code here: https://github.com/FChannel0.">
+<meta name="description" content="{{ .PreferredUsername }} is a federated image board based on ActivityPub. The current version of the code running on the server is still a work-in-progress product, expect a bumpy ride for the time being. Get the server code here: https://github.com/FChannel0.">
<meta property="og:locale" content="en_US" />
<meta property="og:type" content="website" />
@@ -21,23 +21,17 @@
<div style="text-align: center; max-width: 800px; margin: 0 auto;">
<h1>{{ .Title }}</h1>
- <div style="margin-top:50px;">
- <table style="text-align: left;">
-
+ <div class="newsbox" style="margin-top:50px;padding-top:0;">
{{ range $i, $e := .NewsItems }}
- <tr>
- <td>
- <div class="box" style="width:800px; padding: 25px; margin-bottom: 25px;">
- {{ if $.Board.ModCred }}<a href="/{{ $.Key }}/newsdelete/{{ $e.Time }}">[Delete] </a>{{end}}
- <a href="/news/{{.Time}}">{{unixtoreadable $e.Time}} - {{$e.Title}}</a>
- <br><p style="margin-left: 25px;">{{$e.Content}}</p>
- </div>
- </td>
- </tr>
+ <div class="newsbox-news">
+ <h3><a href="/news/{{.Time}}">{{unixtoreadable $e.Time}} - {{$e.Title}}</a>{{ if $.Board.ModCred }} <a href="/{{ $.Key }}/newsdelete/{{ $e.Time }}">[Delete] </a>{{end}}</h3>
+ <br>
+
+ <p>{{$e.Content}}</p>
+ </div>
{{ end }}
- </table>
</div>
-
+
</div>
{{ end }}
{{ define "bottom" }}{{ end }}
diff --git a/static/archive.html b/static/archive.html
index b08693a..3e040bc 100644
--- a/static/archive.html
+++ b/static/archive.html
@@ -20,7 +20,7 @@
{{ end }}
{{ define "top" }}
-<h1 style="text-align: center; color: #af0a0f;">/{{ .Board.Name }}/ - {{ .Board.PrefName }}</h1>
+<h1>/{{ .Board.Name }}/ - {{ .Board.PrefName }}</h1>
<p style="text-align: center;">{{ .Board.Summary }}</p>
<h1 style="text-align: center;">Archived Posts</h1>
{{ end }}
diff --git a/static/bottom.html b/static/bottom.html
index 9d920c0..023deeb 100644
--- a/static/bottom.html
+++ b/static/bottom.html
@@ -1,20 +1,20 @@
{{ define "bottom" }}
<div id="reply-box" class="popup-box" style="display: none;">
- <div id="reply-header" style="display: inline-block; width: 370px; z-index: 0; cursor: move;"></div><div id="reply-close" style="display: inline-block; float: right;"><a href="javascript:closeReply()">[X]</a></div>
+ <div id="reply-header" style="display: inline-block; z-index: 0; cursor: move;"></div><div id="reply-close" style="display: inline-block; float: right;"><a href="javascript:closeReply()">[X]</a></div>
<form onsubmit="sessionStorage.setItem('element-closed-reply', true)" id="reply-post" action="/post" method="post" enctype="multipart/form-data">
<input id="reply-name" name="name" size="43" type="text" placeholder="Name" maxlength="100">
<input id="reply-options" name="options" size="43" type="text" placeholder="Options" maxlength="100">
- <textarea id="reply-comment" name="comment" rows="12" cols="54" style="width: 396px;" maxlength="2000" oninput="sessionStorage.setItem('element-reply-comment', document.getElementById('reply-comment').value)"></textarea>
+ <textarea id="reply-comment" name="comment" rows="12" cols="54" maxlength="2000" oninput="sessionStorage.setItem('element-reply-comment', document.getElementById('reply-comment').value)"></textarea>
<input id="reply-file" name="file" type="file">
- <input id="reply-submit" type="submit" value="Reply" style="float: right;"><br><br>
+ <input id="reply-submit" type="submit" value="Reply" style="float: right;">
<input type="hidden" id="inReplyTo-box" name="inReplyTo" value="{{ .Board.InReplyTo }}">
<input type="hidden" id="sendTo" name="sendTo" value="{{ .Board.To }}">
<input type="hidden" id="boardName" name="boardName" value="{{ .Board.Name }}">
<input type="hidden" id="captchaCode" name="captchaCode" value="{{ .Board.CaptchaCode }}">
- <input type="hidden" id="returnTo" name="returnTo" value="{{ .ReturnTo }}">
- <input type="checkbox" name="sensitive"><span>Mark attachment as sensitive</span><br><br>
+ <input type="hidden" id="returnTo" name="returnTo" value="{{ .ReturnTo }}"><br>
+ <input type="checkbox" name="sensitive"><span>Mark attachment as sensitive</span><br>
<div style="width: 202px; margin: 0 auto; padding-top: 12px;">
- <label for="captcha">Captcha:</label><br>
+ <label for="captcha">Captcha:</label><br>
<input style="display: inline-block;" type="text" id="captcha" name="captcha" autocomplete="off"><br>
</div>
<div style="width: 230px; margin: 0 auto;">
@@ -23,19 +23,20 @@
</form>
</div>
-<div id="report-box" class="popup-box" style="display: none; ">
- <div id="report-header" style="text-align: center; display: inline-block; width: 370px; z-index: 0; cursor: move;"></div><div id="report-close" style="display: inline-block; float: right;"><a href="javascript:closeReport()">[X]</a></div>
+<div id="report-box" class="popup-box" style="display: none;">
+ <div id="report-header" style="text-align: center; display: inline-block; z-index: 0; cursor: move;"></div><div id="report-close" style="display: inline-block; float: right;"><a href="javascript:closeReport()">[X]</a></div>
<form onsubmit="sessionStorage.setItem('element-closed-report', true)" id="report-post" action="/report" method="post">
- <label for="comment">Reason:</label>
+ <label for="comment">Reason:</label><br>
<textarea id="report-comment" name="comment" rows="12" cols="54" style="width: 396px;" maxlength="100" oninput="sessionStorage.setItem('element-report-comment', document.getElementById('report-comment').value)"></textarea>
+ <br>
<input id="report-submit" type="submit" value="Report" style="float: right;">
<input type="hidden" id="report-inReplyTo-box" name="id" value="{{ .Board.InReplyTo }}">
<input type="hidden" id="sendTo" name="sendTo" value="{{ .Board.To }}">
<input type="hidden" id="boardName" name="board" value="{{ .Board.Name }}">
- <input type="hidden" name="close" value="0">
+ <input type="hidden" name="close" value="0">
<input type="hidden" id="captchaCode" name="captchaCode" value="{{ .Board.CaptchaCode }}">
<div style="width: 202px; margin: 0 auto; padding-top: 12px;">
- <label for="captcha">Captcha:</label><br>
+ <label for="captcha">Captcha:</label><br>
<input style="display: inline-block;" type="text" id="captcha" name="captcha" autocomplete="off"><br>
</div>
<div style="width: 230px; margin: 0 auto;">
diff --git a/static/css/themes/default.css b/static/css/themes/default.css
new file mode 100644
index 0000000..8b93e51
--- /dev/null
+++ b/static/css/themes/default.css
@@ -0,0 +1,197 @@
+a, a:link, a:visited, a:hover, a:active {
+ text-decoration: none
+}
+
+a:link, a:visited, a:active {
+ color: black;
+}
+
+a:hover {
+ color: #de0808;
+}
+
+body {
+ background-color: #eef2fe;
+ color: black;
+}
+
+body.nsfw {
+ background-color: #ffffee;
+ color: #820404
+}
+
+h1, h2, h3, h4, h5, h6 {
+ color: #af0a0f;
+}
+
+.popup-box {
+ border: 4px solid #d3caf0;
+ background-color: #eff5ff;
+}
+
+.nsfw .popup-box {
+ border: 4px solid #f0e2d9;
+ background-color: #f9f9e0;
+}
+
+.box {
+ background-color: #eff5ff;
+}
+
+.nsfw .box {
+ background-color: #f9f9e0;
+}
+
+.box-alt {
+ background-color: #d3caf0;
+}
+
+.nsfw .box-alt {
+ background-color: #f0e2d9;
+}
+
+
+.quote {
+ color: #789922;
+}
+
+.post {
+ background-color: #d5daf0;
+}
+
+.nsfw .post {
+ background-color: #f0e0d6;
+}
+
+:target > div > .post {
+ background-color: #d6bad0;
+}
+
+.nsfw :target > div > .post {
+ background-color: #f0c0b0;
+}
+
+.title {
+ color: #0f0c5d;
+}
+
+.name, .tripcode {
+ color: #117743;
+}
+
+a.reply {
+ color: #af0a0f;
+}
+
+.replyLink {
+ color: #000080;
+ font-size: 0.8em;
+}
+
+#newpostbtn {
+ text-align: center;
+ margin-top: 80px;
+}
+
+#postForm {
+ margin: auto;
+}
+
+#postForm tr > td:first-child {
+ background-color: #98e;
+ border: 1px black;
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+}
+
+.nsfw #postForm tr > td:first-child {
+ background-color: #ea8;
+}
+
+#postForm input[type="text"],
+#postForm textarea,
+#reply-name, #reply-options, #reply-comment {
+ box-sizing: border-box;
+ -webkit-box-sizing:border-box;
+ -moz-box-sizing: border-box;
+}
+
+#reply-name, #reply-options, #reply-comment {
+ width: 100%;
+}
+
+#postForm #captcha {
+ display: block;
+ width: 100%;
+}
+
+.popup-box {
+ position: fixed;
+ min-width: 300px;
+ width: min-content;
+ z-index: 9;
+ display: block;
+}
+
+/* TODO: rename */
+.box2 {
+ border: 4px solid #f0e2d9;
+ background-color: #f9f9e0;
+}
+
+.newsbox {
+ padding: 25px;
+ border: 4px solid #f0e2d9;
+ background-color: #f9f9e0;
+}
+
+.newsbox h2 {
+ margin: 0;
+ padding: 0;
+}
+
+.newsbox-news {
+ text-align: left;
+ margin-top: 25px;
+ padding: 25px;
+}
+
+.newsbox-news p,
+.newsbox-news h3 {
+ margin: 0;
+}
+
+#stopTablePost {
+ float: right;
+ display: none;
+}
+
+#boardGrid {
+ display: grid;
+ grid-auto-columns: 1fr;
+ border: 4px solid #820404;
+ background-color: #f9f9e0;
+}
+
+#boardGridHeader {
+ border-bottom: 2px solid #820404;
+ display: inline-grid;
+}
+
+.boardGridCell {
+ white-space: nowrap;
+ display: inline-grid;
+ text-align: left;
+ padding: 5px;
+ border-top: 2px solid #820404;
+ border-left: 2px solid #820404;
+}
+
+/* these may or may not work. my CSS is poor so i just kinda did stuff until it worked. */
+.boardGridCell:nth-child(-n+4) {
+ border-top: none;
+}
+
+.boardGridCell:nth-child(3n+2) {
+ border-left: none;
+}
diff --git a/static/css/themes/gruvbox.css b/static/css/themes/gruvbox.css
new file mode 100644
index 0000000..b534bed
--- /dev/null
+++ b/static/css/themes/gruvbox.css
@@ -0,0 +1,175 @@
+a, a:link, a:visited, a:active {
+ color: #b16286;
+ text-decoration: none
+}
+
+a.reply {
+ color: #cc241d;
+}
+
+a:hover.reply {
+ color: #fb4934;
+}
+
+body {
+ background: #282828;
+ color: #ebdbb2;
+
+ font-family: monospace, sans-serif;
+ font-size: 0.9em;
+}
+
+.popup-box {
+ border: 4px solid #928374;
+ background-color: #3c3836;
+}
+
+.box, .box-alt {
+ background-color: #3c3836;
+}
+
+.quote {
+ color: #98971a;
+}
+
+.post {
+ background-color: #1d2021;
+}
+
+:target > div > .post {
+ background-color: #504945;
+}
+
+.subject {
+ color: #458588;
+}
+
+.name {
+ color: #b8bb26;
+}
+
+.tripcode {
+ color: #689d6a;
+}
+
+h1,h2,h3,h4,h5,h6 {
+ color: #fb4934;
+ margin-bottom: 0.1em;
+}
+
+.replyLink {
+ color: #83a598;
+ font-size: 0.8em;
+}
+
+#newpostbtn {
+ text-align: center;
+ margin-top: 80px;
+}
+
+input[type="text"] {
+ -webkit-appearance: none;
+ -webkit-border-radius: 0;
+}
+
+#postForm {
+ border: 4px solid #928374;
+ background-color: #3c3836;
+ margin: auto;
+
+}
+
+#postForm tr > td:first-child {
+ background-color: #504945;
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+}
+
+#postForm input[type="text"],
+#postForm textarea,
+#reply-name, #reply-options, #reply-comment {
+ box-sizing: border-box;
+ -webkit-box-sizing:border-box;
+ -moz-box-sizing: border-box;
+}
+
+#reply-name, #reply-options, #reply-comment {
+ width: 100%;
+}
+
+#postForm #captcha {
+ display: block;
+ width: 100%;
+}
+
+.popup-box {
+ position: fixed;
+ min-width: 300px;
+ width: min-content;
+ z-index: 9;
+ display: block;
+}
+
+/* TODO: rename */
+.box2 {
+ border: 4px solid #928374;
+ background-color: #3c3836;
+}
+
+.newsbox {
+ padding: 25px;
+ border: 4px solid #928374;
+ background-color: #3c3836;
+}
+
+.newsbox h2 {
+ margin: 0;
+ padding: 0;
+}
+
+.newsbox-news {
+ text-align: left;
+ background-color: #504945;
+ margin-top: 25px;
+ padding: 25px;
+}
+
+.newsbox-news p,
+.newsbox-news h3 {
+ margin: 0;
+}
+
+#stopTablePost {
+ float: right;
+ display: none;
+}
+
+#boardGrid {
+ display: grid;
+ grid-auto-columns: 1fr;
+ border: 4px solid #928374;
+ background-color: #3c3836;
+}
+
+#boardGridHeader {
+ border-bottom: 2px solid #928374;
+ display: inline-grid;
+}
+
+.boardGridCell {
+ white-space: nowrap;
+ display: inline-grid;
+ text-align: left;
+ padding: 5px;
+ border-top: 2px solid #928374;
+ border-left: 2px solid #928374;
+}
+
+/* these may or may not work. my CSS is poor so i just kinda did stuff until it worked. */
+.boardGridCell:nth-child(-n+4) {
+ border-top: none;
+}
+
+.boardGridCell:nth-child(3n+2) {
+ border-left: none;
+}
diff --git a/static/index.html b/static/index.html
index b706e6b..d45dd77 100644
--- a/static/index.html
+++ b/static/index.html
@@ -1,6 +1,6 @@
{{ define "header" }}
<title>{{ .Title }}</title>
-<meta name="description" content="{{ .PreferredUsername }} is a federated image board based on ActivityPub. The current version of the code running on the server is still a work-in-progress product, expect a bumpy ride for the time being. Get the server code here: https://github.com/FChannel0.">
+<meta name="description" content="{{ .PreferredUsername }} is a federated image board based on ActivityPub. The current version of the code running on the server is still a work-in-progress product, expect a bumpy ride for the time being. Get the server code here: https://github.com/FChannel0.">
<meta property="og:locale" content="en_US" />
<meta property="og:type" content="website" />
@@ -23,47 +23,43 @@
<p style="text-align: justify">{{ .PreferredUsername }} is a federated image board based on <a href="https://activitypub.rocks/">ActivityPub</a>. The current version of the code running on the server is still a work-in-progress product, expect a bumpy ride for the time being. Get the server code here: <a href="https://github.com/FChannel0">https://github.com/FChannel0</a>.</p>
{{ if .Boards }}
- {{ $l := len .Boards }}
+ {{ $l := len .Boards }}
<div style="margin-top:50px;">
- <div style="display: grid;border-right: 2px solid #820404">
+ <div id="boardGrid">
{{ if lt $l 2 }}
- <div style="display: inline-grid; border-bottom: 2px solid #820404;border-left: 2px solid #820404;border-top: 2px solid #820404;"><span style="font-size: 1.5em;font-weight: bold;">Local boards</span></div>
+ <div id="boardGridHeader"><span style="font-size: 1.5em;font-weight: bold;">Local boards</span></div>
{{ else if eq $l 2 }}
- <div style="display: inline-grid; grid-column: 1 / 3; border-bottom: 2px solid #820404;border-left: 2px solid #820404;border-top: 2px solid #820404;"><span style="font-size: 1.5em;font-weight: bold;">Local boards</span></div>
+ <div id="boardGridHeader" style="grid-column: 1 / 3;"><span style="font-size: 1.5em;font-weight: bold;">Local boards</span></div>
{{ else }}
- <div style="display: inline-grid;grid-column: 1 / 4;border-bottom: 2px solid #820404;border-left: 2px solid #820404;border-top: 2px solid #820404;"><span style="font-size: 1.5em;font-weight: bold;">Local boards</span></div>
- {{ end }}
+ <div id="boardGridHeader" style="grid-column: 1 / 4;"><span style="font-size: 1.5em;font-weight: bold;">Local boards</span></div>
+ {{ end }}
{{ range .Boards }}
- <div style="whitespace: nowrap;display: inline-grid;text-align: left;padding: 5px;border-bottom: 2px solid #820404;border-left: 2px solid #820404;"><a href="{{.Location}}"><b>/{{.Name}}/</b> - {{.PrefName}} {{ if not .Restricted }} [NSFW] {{ end }}</a></div>
+ <div class="boardGridCell"><a href="{{.Location}}"><b>/{{.Name}}/</b> - {{.PrefName}} {{ if not .Restricted }} [NSFW] {{ end }}</a></div>
{{ end }}
- {{ if gt $l 2 }}
+ {{ if gt $l 2 }}
{{ range .BoardRemainer }}
- <div style="whitespace: nowrap;display: inline-grid;text-align: left;padding: 5px;border-bottom: 2px solid #820404;border-left: 2px solid #820404;"></div>
+ <div class="boardGridCell"></div>
{{ end }}
{{ end }}
- </div>
+ </div>
</div>
{{ end }}
-
- {{ if .NewsItems }}
- <div class="popup-box" style="margin-top:50px;">
- <table style="text-align: left; margin: 25px;">
- <th>
- <tr><h4><a href="/news">{{ .PreferredUsername }} News</a></h4></tr>
- </th>
+
+ {{ if .NewsItems }}
+ <div class="newsbox" style="margin-top:50px;">
+ <h2><a href="/news">{{ .PreferredUsername }} News</a></h2>
{{ range $i, $e := .NewsItems }}
- <tr>
- <td>{{ if $.Board.ModCred }}<a href="/{{ $.Key }}/newsdelete/{{ $e.Time }}">[Delete] </a>{{end}}
- <a href="/news/{{.Time}}">{{unixtoreadable $e.Time}} - {{$e.Title}}</a>
- <br><p style="margin-left: 25px;">{{$e.Content}}</p>
- </td>
- </tr>
+ <div class="newsbox-news">
+ <h3><a href="/news/{{.Time}}">{{unixtoreadable $e.Time}} - {{$e.Title}}</a>{{ if $.Board.ModCred }} <a href="/{{ $.Key }}/newsdelete/{{ $e.Time }}">[Delete] </a>{{end}}</h3>
+ <br>
+
+ <p>{{$e.Content}}</p>
+ </div>
{{ end }}
- </table>
</div>
- {{ end }}
-
- <div class="popup-box" style="margin-top:50px;">
+ {{ end }}
+
+ <div class="box2" style="margin-top:50px;">
<h4 style="margin-bottom:5px;">Current known instances</h4>
<span>(always use a proxy)</span>
<table style="text-align: left; margin: 25px;">
diff --git a/static/js/posts.js b/static/js/posts.js
index 455e7ea..29541c0 100644
--- a/static/js/posts.js
+++ b/static/js/posts.js
@@ -2,29 +2,17 @@ function startNewPost(){
var el = document.getElementById("newpostbtn");
el.style="display:none;";
el.setAttribute("state", "1");
- document.getElementById("newpost").style = "display: block;";
+ document.getElementById("newpost").style = "";
+ document.getElementById("stopTablePost").style = "display:unset;";
+ sessionStorage.setItem("newpostState", true);
}
function stopNewPost(){
var el = document.getElementById("newpostbtn");
- el.style="display:block;";
+ el.style="display:block;margin-bottom:100px;";
el.setAttribute("state", "0");
- document.getElementById("newpost").style = "display: hidden;";
-}
-
-function newpost()
-{
- var state = document.getElementById("newpostbtn").getAttribute("state");
- if(state === "0")
- {
- startNewPost();
- sessionStorage.setItem("newpostState", true);
- }
- else
- {
- stopNewPost();
- sessionStorage.setItem("newpostState", false);
- }
+ document.getElementById("newpost").style = "display: none;";
+ sessionStorage.setItem("newpostState", false);
}
function shortURL(actorName, url)
@@ -182,7 +170,7 @@ function quote(actorName, opid, id)
var h = document.getElementById(id + "-content").offsetTop - 348;
}
- const boxStyle = "display: block; position: absolute; width: 400px; height: 600px; z-index: 9; top: " + h + "px; left: " + w + "px; padding: 5px;";
+ const boxStyle = "top: " + h + "px; left: " + w + "px;";
box.setAttribute("style", boxStyle);
sessionStorage.setItem("element-reply-style", boxStyle);
sessionStorage.setItem("reply-top", h);
@@ -202,7 +190,6 @@ function quote(actorName, opid, id)
sessionStorage.setItem("element-reply-comment", comment.value);
dragElement(header);
-
}
function report(actorName, id)
@@ -216,7 +203,7 @@ function report(actorName, id)
var w = window.innerWidth / 2 - 200;
var h = document.getElementById(id + "-content").offsetTop - 348;
- const boxStyle = "display: block; position: absolute; width: 400px; height: 480px; z-index: 9; top: " + h + "px; left: " + w + "px; padding: 5px;";
+ const boxStyle = "top: " + h + "px; left: " + w + "px;";
box.setAttribute("style", boxStyle);
sessionStorage.setItem("element-report-style", boxStyle);
sessionStorage.setItem("report-top", h);
diff --git a/static/js/themes.js b/static/js/themes.js
new file mode 100644
index 0000000..3f1b906
--- /dev/null
+++ b/static/js/themes.js
@@ -0,0 +1,40 @@
+function setCookie(key, value, age) {
+ document.cookie = key + "=" + encodeURIComponent(value) + ";sameSite=strict;max-age=" + 60 * 60 * 24 * age + ";path=/";
+}
+
+function getCookie(key) {
+ if (document.cookie.length != 0) {
+ return document.cookie.split('; ').find(row => row.startsWith(key)).split('=')[1];
+ }
+ return "";
+}
+
+function setTheme(name) {
+ for (let i = 0, tags = document.getElementsByTagName("link"); i < tags.length; i++) {
+ if (tags[i].type === "text/css" && tags[i].title) {
+ tags[i].disabled = !(tags[i].title === name);
+ }
+ }
+
+ setCookie("theme", name, 3650);
+}
+
+function applyTheme() {
+ // HACK: disable all of the themes first. this for some reason makes things work.
+ for (let i = 0, tags = document.getElementsByTagName("link"); i < tags.length; i++) {
+ if (tags[i].type === "text/css" && tags[i].title) {
+ tags[i].disabled = true;
+ }
+ }
+ let theme = getCookie("theme") || "default";
+ setTheme(theme);
+
+ // reflect this in the switcher
+ let switcher = document.getElementById("themeSwitcher");
+ for(var i = 0; i < switcher.options.length; i++) {
+ if (switcher.options[i].value === theme) {
+ switcher.selectedIndex = i;
+ break;
+ }
+ }
+}
diff --git a/static/main.html b/static/main.html
index 362b7b3..3e29053 100644
--- a/static/main.html
+++ b/static/main.html
@@ -8,81 +8,13 @@
<meta property="og:locale" content="en_US" />
<meta property="og:type" content="website" />
<link rel="icon" type="image/png" href="/static/favicon.png">
- <style>
- a, a:link, a:visited, a:hover, a:active {
- text-decoration: none
- }
-
- a:link, a:visited, a:active {
- color: black;
- }
-
- a:hover {
- color: #de0808;
- }
-
- body {
- {{ if .Board.Restricted }}
- background-color: #eef2fe;
- color: black;
- {{ else }}
- background-color: #ffffee;
- color: #820404
- {{ end }}
- }
-
- .popup-box {
- {{ if .Board.Restricted }}
- border: 4px solid #d3caf0;
- background-color: #eff5ff;
- {{ else }}
- border: 4px solid #f0e2d9;
- background-color: #f9f9e0;
- {{ end }}
- }
-
- .box {
- {{ if .Board.Restricted }}
- background-color: #eff5ff;
- {{ else }}
- background-color: #f9f9e0;
- {{ end }}
- }
-
- .box-alt {
- {{ if .Board.Restricted }}
- background-color: #d3caf0;
- {{ else }}
- background-color: #f0e2d9;
- {{ end }}
- }
-
- .quote {
- color: #789922;
- }
-
- .post {
- {{ if .Board.Restricted }}
- background-color: #d5daf0;
- {{ else }}
- background-color: #f0e0d6;
- {{ end }}
- }
-
- :target > div > .post {
- {{ if .Board.Restricted }}
- background-color: #d6bad0;
- {{ else }}
- background-color: #f0c0b0;
- {{ end }}
- }
- .tripcode {
- color: #117743;
- }
- </style>
+ <link rel="stylesheet" type="text/css" href="/static/css/themes/default.css" title="default">
+ {{ range .Themes }}
+ <link rel="alternate stylesheet" type="text/css" href="/static/css/themes/{{.}}.css" title="{{.}}" disabled>
+ {{ end }}
{{ template "header" . }}
</head>
- <body>
+ <body {{ if not .Board.Restricted }}class="nsfw"{{ end }} onload="applyTheme()">
<ul style="display: inline; padding:0;">
{{ $l := len .Boards }}
<li style="display: inline;">[<a href="/">Home</a>]</li>
@@ -108,12 +40,25 @@
{{ template "content" . }}
{{ template "bottom" . }}
+
<div align="center" style="width: 500px; margin:0 auto; margin-top: 50px;">
<a href="/">[Home]</a><a href="/static/rules.html">[Rules]</a><a href="/static/faq.html">[FAQ]</a>
<p>All trademarks and copyrights on this page are owned by their respective parties.</p>
</div>
+
+ <div style="float: right;">
+ Theme:
+ <select id="themeSwitcher" onchange="setTheme(this.options[this.selectedIndex].value)">
+ <option value="default">default</option>
+ {{ range .Themes }}
+ <option value="{{.}}">{{.}}</option>
+ {{ end }}
+ </select>
+ </div>
+
+ <script src="/static/js/themes.js"></script>
+{{ template "script" . }}
</body>
</html>
-{{ template "script" . }}
{{ end }}
diff --git a/static/manage.html b/static/manage.html
index 3bd621b..dc25468 100644
--- a/static/manage.html
+++ b/static/manage.html
@@ -20,7 +20,7 @@
{{ $board := .Board }}
{{ $key := .Key }}
{{ if .IsLocal }}
-<div id="following" class="popup-box" style="margin-bottom: 25px; margin-top: 5px; padding: 12px;">
+<div id="following" class="box2" style="margin-bottom: 25px; margin-top: 5px; padding: 12px;">
<h4 style="margin: 0; margin-bottom: 5px;">Following</h4>
{{ if .AutoSubscribe }}<a title="Auto Follow is On" href="/autosubscribe?board={{ .Board.Name }}">[Toggle Auto Follow Off]{{ else }}<a title="Auto Follow is Off" href="/autosubscribe?board={{ .Board.Name }}">[Toggle Auto Follow On]{{ end }}</a>
<form id="follow-form" action="/{{ .Key }}/{{ .Board.Name }}/follow" method="post" enctype="application/x-www-form-urlencoded" style="margin-top: 5px;">
@@ -36,7 +36,7 @@
</ul>
</div>
-<div id="followers" class="popup-box" style="margin-bottom: 25px; padding: 12px;">
+<div id="followers" class="box2" style="margin-bottom: 25px; padding: 12px;">
<h4 style="margin: 0; margin-bottom: 5px;">Followers</h4>
<ul style="display: inline-block; padding: 0; margin: 0; list-style-type: none;">
{{ range .Followers }}
@@ -46,7 +46,7 @@
</div>
{{ end }}
-<div id="reported" class="popup-box" style="margin-bottom: 25px; padding: 12px;">
+<div id="reported" class="box2" style="margin-bottom: 25px; padding: 12px;">
<h4 style="margin: 0; margin-bottom: 5px;">Reported</h4>
<ul style="display: inline-block; padding: 0; margin: 0; list-style-type: none;">
{{ $domain := .Domain }}
diff --git a/static/nadmin.html b/static/nadmin.html
index 5e38151..e8fbc36 100644
--- a/static/nadmin.html
+++ b/static/nadmin.html
@@ -26,7 +26,7 @@
</div>
-<div id="following" class="popup-box" style="margin-bottom: 25px; padding: 12px;">
+<div id="following" class="box2" style="margin-bottom: 25px; padding: 12px;">
<h4 style="margin: 0; margin-bottom: 5px;">Subscribed</h4>
<form id="follow-form" action="/{{ .Key }}/follow" method="post" enctype="application/x-www-form-urlencoded">
<input id="follow" name="follow" style="margin-bottom: 12px;" placeholder="http://localhost:3000/g"></input><input type="submit" value="Subscribe"><br>
@@ -41,7 +41,7 @@
</ul>
</div>
-<div id="followers" class="popup-box" style="margin-bottom: 25px; padding: 12px; display:none;">
+<div id="followers" class="box2" style="margin-bottom: 25px; padding: 12px; display:none;">
<h4 style="margin: 0; margin-bottom: 5px;">Followers</h4>
<ul style="display: inline-block; padding: 0; margin: 0; list-style-type: none;">
{{ range .Followers }}
@@ -50,7 +50,7 @@
</ul>
</div>
-<div class="popup-box" style="margin-bottom: 25px; padding: 12px;">
+<div class="box2" style="margin-bottom: 25px; padding: 12px;">
<h3>Create News</h3>
<form id="news" action="/{{ .Key }}/postnews" method="post" enctype="application/x-www-form-urlencoded">
<label>Title:</label><br>
@@ -60,7 +60,7 @@
</form>
</div>
-<div id="regex" class="popup-box" style="margin-bottom: 25px; padding: 12px;">
+<div id="regex" class="box2" style="margin-bottom: 25px; padding: 12px;">
<h3>Regex Post Blacklist</h3>
<form id="blacklist" action="/blacklist" method="post" enctype="application/x-www-form-urlencoded">
<label>Regex:</label><br>
diff --git a/static/ncatalog.html b/static/ncatalog.html
index e35edd0..ec8afd5 100644
--- a/static/ncatalog.html
+++ b/static/ncatalog.html
@@ -17,14 +17,16 @@
{{ define "content" }}
{{ $board := .Board }}
<hr>
-<ul style="margin: 0; padding: 0; display: inline">
- <li style="display: inline"><a href="/{{ $board.Name }}">[Return]</a></li>
+
+<div class="navlinks">
+ [<a href="/{{ $board.Name }}/">Return</a>]
{{ if showArchive }}
- <li style="display: inline"><a href="/{{ $board.Name }}/archive">[Archive]</a></li>
+ [<a href="/{{ $board.Name }}/archive">Archive</a>]
{{ end }}
- <li style="display: inline"><a href="#bottom">[Bottom]</a></li>
- <li style="display: inline"><a href="javascript:location.reload()">[Refresh]</a></li>
-</ul>
+ [<a href="#bottom">Bottom</a>]
+ [<a href="javascript:location.reload()">Refresh</a>]
+</div>
+
<hr>
<div style="padding: 10px; text-align: center;">
@@ -39,7 +41,12 @@
<a href="/marksensitive?id={{ .Id }}&board={{ $board.Actor.Name }}">[Mark Sensitive]</a>
{{ end }}
<div id="hide-{{ .Id }}" style="display: none;">[Hide]</div>
- <div id="sensitive-{{ .Id }}" style="display: none;"><div style="position: relative; text-align: center;"><img id="sensitive-img-{{ .Id }}" style="float: left; margin-right: 10px; margin-bottom: 10px; max-width: 180px; max-height: 180px;" src="/static/sensitive.png"><div id="sensitive-text-{{ .Id }}" style="width: 170px; position: absolute; margin-top: 75px; padding: 5px; background-color: black; color: white; cursor: default; ">NSFW Content</div></div></div>
+ <div id="sensitive-{{ .Id }}" style="display: none;">
+ <div style="position: relative; text-align: center;">
+ <img id="sensitive-img-{{ .Id }}" style="float: left; margin-right: 10px; margin-bottom: 10px; max-width: 180px; max-height: 180px;" src="/static/sensitive.png">
+ <div id="sensitive-text-{{ .Id }}" style="width: 170px; position: absolute; margin-top: 75px; padding: 5px; background-color: black; color: white; cursor: default; ">NSFW Content</div>
+ </div>
+ </div>
<a id="{{ .Id }}-anchor" href="/{{ $board.Name }}/{{ short $board.Actor.Outbox .Id}}">
<div id="media-{{ .Id }}" style="width:180px;"> {{ parseAttachment . true }}</div>
</a>
@@ -67,33 +74,37 @@
}
</script>
{{ end }}
- <a id="{{ .Id }}-link" href="/{{ $board.Name }}/{{ short $board.Actor.Outbox .Id }}">
- <div>
- {{ $replies := .Replies }}
- {{ if $replies }}
- <span style="display: block;">R: {{ $replies.TotalItems }}{{ if $replies.TotalImgs }}/ A: {{ $replies.TotalImgs }}{{ end }}</span>
- {{ end }}
- {{ if .Name }}
- <span style="display: block; color: #0f0c5d;"><b>{{ .Name }}</b></span>
- {{ end }}
+ <a style="color: unset;" id="{{ .Id }}-link" href="/{{ $board.Name }}/{{ short $board.Actor.Outbox .Id }}">
+ <div style="display: block;">
+ {{ $replies := .Replies }}
+ {{ if $replies }}
+ <span>R: {{ $replies.TotalItems }}{{ if $replies.TotalImgs }}/ A: {{ $replies.TotalImgs }}{{ end }}</span>
+ {{ end }}
+ {{ if .Name }}
+ <br>
+ <span class="subject"><b>{{ .Name }}</b></span>
+ {{ end }}
- {{ if .Content }}
- <span style="display: block">{{.Content}}</span>
- {{ end }}
- </div>
+ {{ if .Content }}
+ <br>
+ <span>{{.Content}}</span>
+ {{ end }}
+ </div>
</a>
</div>
{{ end }}
</div>
<hr>
-<ul style="margin: 0; padding: 0; display: inline">
- <li style="display: inline"><a href="/{{ $board.Name }}">[Return]</a></li>
+
+<div class="navlinks">
+ [<a href="/{{ $board.Name }}/">Return</a>]
{{ if showArchive }}
- <li style="display: inline"><a href="/{{ $board.Name }}/archive">[Archive]</a></li>
+ [<a href="/{{ $board.Name }}/archive">Archive</a>]
{{ end }}
- <li style="display: inline"><a id="bottom" href="#top">[Top]</a></li>
- <li style="display: inline"><a href="javascript:location.reload()">[Refresh]</a></li>
-</ul>
+ [<a href="#top">Top</a>]
+ [<a href="javascript:location.reload()">Refresh</a>]
+</div>
+
<hr>
{{ end }}
{{ define "bottom" }}
diff --git a/static/news.html b/static/news.html
index 65ae410..a83c406 100644
--- a/static/news.html
+++ b/static/news.html
@@ -1,6 +1,6 @@
{{ define "header" }}
<title>{{ .Title }}</title>
-<meta name="description" content="{{ .PreferredUsername }} is a federated image board based on ActivityPub. The current version of the code running on the server is still a work-in-progress product, expect a bumpy ride for the time being. Get the server code here: https://github.com/FChannel0.">
+<meta name="description" content="{{ .PreferredUsername }} is a federated image board based on ActivityPub. The current version of the code running on the server is still a work-in-progress product, expect a bumpy ride for the time being. Get the server code here: https://github.com/FChannel0.">
<meta property="og:locale" content="en_US" />
<meta property="og:type" content="website" />
@@ -18,12 +18,14 @@
{{ define "top" }}{{ end }}
{{ define "content" }}
-<div style="text-align: left; max-width: 800px; margin: 0 auto;">
+<div class="newsbox" style="text-align: left; max-width: 800px; margin: 0 auto;margin-top: 50px;padding-top:0;">
{{ range .NewsItems }}
+ <div class="newsbox-news">
<p><h1>{{unixtoreadable .Time}} - {{.Title}}</h1><br>{{.Content}}</p>
+ </div>
{{ end }}
-
+
</div>
{{ end }}
{{ define "bottom" }}{{ end }}
diff --git a/static/nposts.html b/static/nposts.html
index 6def8b2..46d8bb1 100644
--- a/static/nposts.html
+++ b/static/nposts.html
@@ -15,33 +15,35 @@
{{ define "content" }}
{{ $board := .Board }}
<hr>
-<ul style="margin: 0; padding: 0; display: inline">
- <li style="display: inline"><a href="/{{ $board.Name }}/catalog">[Catalog]</a></li>
+<div class="navlinks">
+ [<a href="/{{ $board.Name }}/catalog">Catalog</a>]
{{ if showArchive }}
- <li style="display: inline"><a href="/{{ $board.Name }}/archive">[Archive]</a></li>
+ [<a href="/{{ $board.Name }}/archive">Archive</a>]
{{ end }}
- <li style="display: inline"><a href="#bottom">[Bottom]</a></li>
- <li style="display: inline"><a href="javascript:location.reload()">[Refresh]</a></li>
-</ul>
+ [<a href="#bottom">Bottom</a>]
+ [<a href="javascript:location.reload()">Refresh</a>]
+</div>
{{ template "posts" . }}
<hr>
-<ul style="margin: 0; padding: 0; display: inline">
- <li style="display: inline"><a href="/{{ $board.Name }}/catalog">[Catalog]</a></li>
+
+<div class="navlinks">
+ [<a href="/{{ $board.Name }}/catalog">Catalog</a>]
{{ if showArchive }}
- <li style="display: inline"><a href="/{{ $board.Name }}/archive">[Archive]</a></li>
+ [<a href="/{{ $board.Name }}/archive">Archive</a>]
{{ end }}
- <li style="display: inline"><a id="bottom" href="#top">[Top]</a></li>
- <li style="display: inline"><a href="javascript:location.reload()">[Refresh]</a></li>
-</ul>
+ [<a href="#top" id="bottom">Top</a>]
+ [<a href="javascript:location.reload()">Refresh</a>]
+</div>
+
<hr>
{{ if gt .TotalPage 0 }}
{{ $totalPage := .TotalPage }}
<ul style="float: right; margin: 0; padding: 0; display: inline">
{{ $page := .CurrentPage }}
{{ if gt $page 0 }}
- <li style="display: inline"><a href="/{{ $board.Name }}?page={{ sub $page 1 }}">[ < ]</a></li>
+ <li style="display: inline"><a href="/{{ $board.Name }}?page={{ sub $page 1 }}">[ &lt; ]</a></li>
{{ end }}
{{ range $i, $e := .Pages }}
{{ if eq $i $page}}
@@ -51,7 +53,7 @@
{{ end }}
{{ end }}
{{ if lt .CurrentPage .TotalPage }}
- <li style="display: inline"><a href="/{{ $board.Name }}?page={{ add $page 1 }}">[ > ]</a></li>
+ <li style="display: inline"><a href="/{{ $board.Name }}?page={{ add $page 1 }}">[ &gt; ]</a></li>
{{ end }}
</ul>
{{ end }}
diff --git a/static/posts.html b/static/posts.html
index 50d8b43..7110dd9 100644
--- a/static/posts.html
+++ b/static/posts.html
@@ -45,7 +45,10 @@
}
</script>
{{ end }}
- <span style="color: #0f0c5d;"><b>{{ .Name }}</b></span><span style="color: #117743;"><b>{{ if .AttributedTo }} {{.AttributedTo }} {{ else }} Anonymous {{ end }}</b></span><span class="tripcode"> {{ .TripCode }} </span><span>{{ .Published }} <a id="{{ .Id }}-anchor" href="/{{ $board.Name }}/{{ short $board.Actor.Outbox $opId }}#{{ short $board.Actor.Outbox .Id }}">No.</a> <a id="{{ .Id }}-link" title="{{ .Id }}" {{ if eq .Type "Note" }} href="javascript:quote('{{ $board.Actor.Id }}', '{{ $opId }}', '{{ .Id }}')" {{ end }}>{{ short $board.Actor.Outbox .Id }}</a> {{ if ne .Type "Tombstone" }}<a href="javascript:report('{{ $board.Actor.Id }}', '{{ .Id }}')">[Report]</a>{{ end }}</span>
+ <span class="subject"><b>{{ .Name }}</b></span>
+ <span class="name"><b>{{ if .AttributedTo }} {{.AttributedTo }} {{ else }} Anonymous {{ end }}</b></span>
+ <span class="tripcode"> {{ .TripCode }} </span>
+ <span class="timestamp" data-utc="{{.Published | timeToUnix}}">{{ .Published | timeToReadableLong }} <a id="{{ .Id }}-anchor" href="/{{ $board.Name }}/{{ short $board.Actor.Outbox $opId }}#{{ short $board.Actor.Outbox .Id }}">No.</a> <a id="{{ .Id }}-link" title="{{ .Id }}" {{ if eq .Type "Note" }} href="javascript:quote('{{ $board.Actor.Id }}', '{{ $opId }}', '{{ .Id }}')" {{ end }}>{{ short $board.Actor.Outbox .Id }}</a> {{ if ne .Type "Tombstone" }}<a href="javascript:report('{{ $board.Actor.Id }}', '{{ .Id }}')">[Report]</a>{{ end }}</span>
<p id="{{ .Id }}-content" style="white-space: pre-wrap; margin: 10px 30px 10px 30px;">{{ parseContent $board.Actor $opId .Content $thread }}</p>
{{ if .Replies }}
{{ $replies := .Replies }}
@@ -57,7 +60,7 @@
{{ range $replies.OrderedItems }}
<div id="{{ short $board.Actor.Outbox .Id }}">
<div style="display: inline-block; overflow: auto;">
- <div style="float: left; display: block; margin-right: 5px;">>></div>
+ <div style="float: left; display: block; margin-right: 5px;">&gt;&gt;</div>
<div class="post" style="overflow: auto; padding: 5px; margin-bottom: 2px;">
{{ if eq $board.ModCred $board.Domain $board.Actor.Id }}
<a href="/delete?id={{ .Id }}&board={{ $board.Actor.Name }}">[Delete Post]</a>
@@ -97,7 +100,10 @@
}
</script>
{{ end }}
- <span style="color: #0f0c5d;"><b>{{ .Name }}</b></span><span style="color: #117743;"><b>{{ if .AttributedTo }} {{.AttributedTo }} {{ else }} Anonymous {{ end }}</b></span><span class="tripcode"> {{ .TripCode }} </span><span>{{ .Published }} <a id="{{ .Id }}-anchor" href="/{{ $board.Name }}/{{ short $board.Actor.Outbox $opId }}#{{ short $board.Actor.Outbox .Id }}">No. </a><a id="{{ .Id }}-link" title="{{ .Id }}" {{ if eq .Type "Note" }} href="javascript:quote('{{ $board.Actor.Id }}', '{{ $opId }}', '{{ .Id }}')" {{ end }}>{{ short $board.Actor.Outbox .Id }}</a> {{ if ne .Type "Tombstone" }}<a href="javascript:report('{{ $board.Actor.Id }}', '{{ .Id }}')">[Report]</a>{{ end }}</span>
+ <span class="subject"><b>{{ .Name }}</b></span>
+ <span class="name"><b>{{ if .AttributedTo }} {{.AttributedTo }} {{ else }} Anonymous {{ end }}</b></span>
+ <span class="tripcode"> {{ .TripCode }} </span>
+ <span class="timestamp" data-utc="{{ .Published | timeToUnix }}">{{ .Published | timeToReadableLong }} <a id="{{ .Id }}-anchor" href="/{{ $board.Name }}/{{ short $board.Actor.Outbox $opId }}#{{ short $board.Actor.Outbox .Id }}">No. </a><a id="{{ .Id }}-link" title="{{ .Id }}" {{ if eq .Type "Note" }} href="javascript:quote('{{ $board.Actor.Id }}', '{{ $opId }}', '{{ .Id }}')" {{ end }}>{{ short $board.Actor.Outbox .Id }}</a> {{ if ne .Type "Tombstone" }}<a href="javascript:report('{{ $board.Actor.Id }}', '{{ .Id }}')">[Report]</a>{{ end }}</span>
{{ $parentId := .Id }}
{{ if .Replies.OrderedItems }}
{{ range .Replies.OrderedItems }}
diff --git a/static/top.html b/static/top.html
index 0081fc7..0e265d8 100644
--- a/static/top.html
+++ b/static/top.html
@@ -1,39 +1,59 @@
{{ define "top" }}
-<div style="margin: 0 auto; width: 700px; margin-bottom: 100px;">
- <h1 style="text-align: center; color: #af0a0f;">/{{ .Board.Name }}/ - {{ .Board.PrefName }}</h1>
+<div style="margin: 0 auto; width: 700px;">
+ <h1 style="text-align: center;">/{{ .Board.Name }}/ - {{ .Board.PrefName }}</h1>
<p style="text-align: center;">{{ .Board.Summary }}</p>
{{ $len := len .Posts }}
{{ if eq $len 0 }}
{{ if .Board.InReplyTo }}
- <h3 id="newpostbtn" state="0" style="text-align: center; margin-top: 80px; display: none;"><a href="javascript:newpost()">[Post a Reply]</a></h3>
+ <h3 id="newpostbtn" state="0" style="display: none; margin-bottom:100px;"><a href="javascript:startNewPost()">[Post a Reply]</a></h3>
{{ else }}
- <h3 id="newpostbtn" state="0" style="text-align: center; margin-top: 80px; display: none;"><a href="javascript:newpost()">[Start a New Thread]</a></h3>
+ <h3 id="newpostbtn" state="0" style="display: none; margin-bottom:100px;"><a href="javascript:startNewPost()">[Start a New Thread]</a></h3>
{{ end }} <!-- end if inreplyto-->
<div id="newpost">
- <form onsubmit="sessionStorage.setItem('element-closed-reply', true)" id="new-post" action="/post" method="post" enctype="multipart/form-data" style="margin-left: 180px;">
- <label for="name">Name:</label><br>
- <input type="text" id="name" name="name" placeholder="Anonymous" maxlength="100"><br>
- <label for="options">Options:</label><br>
- <input type="text" id="options" name="options" maxlength="100" style="margin-right:10px">{{ if .Board.InReplyTo }}<input type="submit" value="Post">{{ end }}<br>
- {{ if eq .Board.InReplyTo "" }}
- <label for="subject">Subject:</label><br>
- <input type="text" id="subject" name="subject" maxlength="100" style="margin-right:10px"><input type="submit" value="Post"><br>
- {{ end }}
- <label for="comment">Comment:</label><br>
- <textarea rows="10" cols="50" id="comment" name="comment" maxlength="2000"></textarea><br>
+ <form onsubmit="sessionStorage.setItem('element-closed-reply', true)" id="new-post" action="/post" method="post" enctype="multipart/form-data">
+ <table id="postForm">
+ <tr>
+ <tr>
+ <td><label for="name">Name:</label></td>
+ <td><input type="text" id="name" name="name" placeholder="Anonymous" maxlength="100">
+ <a id="stopTablePost" onclick="stopNewPost()">[X]</a>
+ </td>
+ </tr>
+ <tr>
+ <td><label for="options">Options:</label></td>
+ <td><input type="text" id="options" name="options" maxlength="100" style="margin-right:10px">{{ if .Board.InReplyTo }}<input type="submit" value="Post">{{ end }}</td>
+ </tr>
+ {{ if eq .Board.InReplyTo "" }}
+ <tr>
+ <td><label for="subject">Subject:</label></td>
+ <td><input type="text" id="subject" name="subject" maxlength="100" style="margin-right:10px"><input type="submit" value="Post"></td>
+ </tr>
+ {{ end }}
+ <tr>
+ <td><label for="comment">Comment:</label></td>
+ <td><textarea rows="10" cols="50" id="comment" name="comment" maxlength="2000"></textarea></td>
+ </tr>
+ <tr>
+ <td><label for="file">Image</label></td>
+ <td><input type="file" id="file" name="file" {{ if gt $len 1 }} required {{ else }} {{ if eq $len 0 }} required {{ end }} {{ end }} >
+ <br><input type="checkbox" name="sensitive">Mark sensitive</input></td>
+ </tr>
+ <tr>
+ <td><label for="captcha">Captcha:</label></td>
+ <td>
+ <div style="height: 65px; display: inline;">
+ <img src="{{ .Board.Captcha }}">
+ </div>
+ <input type="text" id="captcha" name="captcha" autocomplete="off">
+ </td>
+ </tr>
+ </table>
+
<input type="hidden" id="inReplyTo" name="inReplyTo" value="{{ .Board.InReplyTo }}">
<input type="hidden" id="sendTo" name="sendTo" value="{{ .Board.To }}">
<input type="hidden" id="boardName" name="boardName" value="{{ .Board.Name }}">
<input type="hidden" id="captchaCode" name="captchaCode" value="{{ .Board.CaptchaCode }}">
<input type="hidden" id="returnTo" name="returnTo" value="{{ .ReturnTo }}">
- <input type="file" id="file" name="file" {{ if gt $len 1 }} required {{ else }} {{ if eq $len 0 }} required {{ end }} {{ end }} ><br><br>
- <input type="checkbox" name="sensitive"><span>Mark attachment as sensitive</span><br><br>
- <label stye="display: inline-block;" for="captcha">Captcha:</label>
- <br>
- <input style="display: inline-block;" type="text" id="captcha" name="captcha" autocomplete="off"><br>
- <div style="height: 65px;">
- <img src="{{ .Board.Captcha }}">
- </div>
</form>
</div>
@@ -41,37 +61,57 @@
{{ if eq (index .Posts 0).Type "Note" }}
{{ if .Board.InReplyTo }}
- <h3 id="newpostbtn" state="0" style="text-align: center; margin-top: 80px; display: none;"><a href="javascript:newpost()">[Post a Reply]</a></h3>
+ <h3 id="newpostbtn" state="0" style="text-align: center; margin-top: 80px; display: none; margin-bottom:100px;"><a href="javascript:startNewPost()">[Post a Reply]</a></h3>
{{ else }}
- <h3 id="newpostbtn" state="0" style="text-align: center; margin-top: 80px; display: none;"><a href="javascript:newpost()">[Start a New Thread]</a></h3>
+ <h3 id="newpostbtn" state="0" style="text-align: center; margin-top: 80px; display: none; margin-bottom:100px;"><a href="javascript:startNewPost()">[Start a New Thread]</a></h3>
{{ end }} <!-- end if inreplyto-->
{{ $len := len .Posts }}
<div id="newpost">
- <form onsubmit="sessionStorage.setItem('element-closed-reply', true)" id="new-post" action="/post" method="post" enctype="multipart/form-data" style="margin-left: 180px;">
- <label for="name">Name:</label><br>
- <input type="text" id="name" name="name" placeholder="Anonymous" maxlength="100"><br>
- <label for="options">Options:</label><br>
- <input type="text" id="options" name="options" maxlength="100" style="margin-right:10px">{{ if .Board.InReplyTo }}<input type="submit" value="Post">{{ end }}<br>
- {{ if eq .Board.InReplyTo "" }}
- <label for="subject">Subject:</label><br>
- <input type="text" id="subject" name="subject" maxlength="100" style="margin-right:10px"><input type="submit" value="Post"><br>
- {{ end }}
- <label for="comment">Comment:</label><br>
- <textarea rows="10" cols="50" id="comment" name="comment" maxlength="2000"></textarea><br>
+ <form onsubmit="sessionStorage.setItem('element-closed-reply', true)" id="new-post" action="/post" method="post" enctype="multipart/form-data">
+ <table id="postForm">
+ <tr>
+ <tr>
+ <td><label for="name">Name:</label></td>
+ <td><input type="text" id="name" name="name" placeholder="Anonymous" maxlength="100">
+ <a id="stopTablePost" onclick="stopNewPost()">[X]</a>
+ </tr>
+ <tr>
+ <td><label for="options">Options:</label></td>
+ <td><input type="text" id="options" name="options" maxlength="100" style="margin-right:10px">{{ if .Board.InReplyTo }}<input type="submit" value="Post">{{ end }}</td>
+ </tr>
+ {{ if eq .Board.InReplyTo "" }}
+ <tr>
+ <td><label for="subject">Subject:</label></td>
+ <td><input type="text" id="subject" name="subject" maxlength="100" style="margin-right:10px"><input type="submit" value="Post"></td>
+ </tr>
+ {{ end }}
+ <tr>
+ <td><label for="comment">Comment:</label></td>
+ <td><textarea rows="10" cols="50" id="comment" name="comment" maxlength="2000"></textarea></td>
+ </tr>
+ <tr>
+ <td><label for="file">Image</label></td>
+ <td><input type="file" id="file" name="file" {{ if gt $len 1 }} required {{ else }} {{ if eq $len 0 }} required {{ end }} {{ end }} >
+ <br><input type="checkbox" name="sensitive">Mark sensitive</input></td>
+ </tr>
+ <tr>
+ <td><label for="captcha">Captcha:</label></td>
+ <td>
+ <div style="height: 65px; display: inline;">
+ <img src="{{ .Board.Captcha }}">
+ </div>
+ <input type="text" id="captcha" name="captcha" autocomplete="off">
+ </td>
+ </tr>
+ </table>
+
<input type="hidden" id="inReplyTo" name="inReplyTo" value="{{ .Board.InReplyTo }}">
<input type="hidden" id="sendTo" name="sendTo" value="{{ .Board.To }}">
<input type="hidden" id="boardName" name="boardName" value="{{ .Board.Name }}">
<input type="hidden" id="captchaCode" name="captchaCode" value="{{ .Board.CaptchaCode }}">
- <input type="hidden" id="returnTo" name="returnTo" value="{{ .ReturnTo }}">
- <input type="file" id="file" name="file" {{ if gt $len 1 }} required {{ else }} {{ if eq $len 0 }} required {{ end }} {{ end }} ><br><br>
- <input type="checkbox" name="sensitive"><span>Mark attachment as sensitive</span><br><br>
- <label stye="display: inline-block;" for="captcha">Captcha:</label>
- <br>
- <input style="display: inline-block;" type="text" id="captcha" name="captcha" autocomplete="off"><br>
- <div style="height: 65px;">
- <img src="{{ .Board.Captcha }}">
- </div>
- </form>
+ <input type="hidden" id="returnTo" name="returnTo" value="{{ .ReturnTo }}"> </form>
+ </div>
+
</div>
{{ else }}
<h1 style="text-align: center;">Archived Post</h1>
diff --git a/tripcode.go b/tripcode.go
index 7e19cf0..b567bf0 100644
--- a/tripcode.go
+++ b/tripcode.go
@@ -1,15 +1,15 @@
package main
import (
- "golang.org/x/text/encoding/japanese"
- "golang.org/x/text/transform"
- "github.com/simia-tech/crypt"
- "strings"
"bytes"
- "regexp"
"database/sql"
_ "github.com/lib/pq"
- "net/http"
+ "github.com/simia-tech/crypt"
+ "golang.org/x/text/encoding/japanese"
+ "golang.org/x/text/transform"
+ "net/http"
+ "regexp"
+ "strings"
)
const SaltTable = "" +
@@ -22,7 +22,6 @@ const SaltTable = "" +
"................................" +
"................................"
-
func TripCode(pass string) string {
pass = TripCodeConvert(pass)
@@ -32,25 +31,25 @@ func TripCode(pass string) string {
s := []rune(pass + "H..")[1:3]
for i, r := range s {
- salt[i] = rune(SaltTable[r % 256])
+ salt[i] = rune(SaltTable[r%256])
}
- enc, err := crypt.Crypt(pass, "$1$" + string(salt[:]))
-
+ enc, err := crypt.Crypt(pass, "$1$"+string(salt[:]))
+
CheckError(err, "crypt broke")
-
- return enc[len(enc) - 10 : len(enc)]
+
+ return enc[len(enc)-10 : len(enc)]
}
func TripCodeSecure(pass string) string {
pass = TripCodeConvert(pass)
- enc, err := crypt.Crypt(pass, "$1$" + Salt)
-
+ enc, err := crypt.Crypt(pass, "$1$"+Salt)
+
CheckError(err, "crypt secure broke")
-
- return enc[len(enc) - 10 : len(enc)]
+
+ return enc[len(enc)-10 : len(enc)]
}
func TripCodeConvert(str string) string {
@@ -76,30 +75,30 @@ func CreateNameTripCode(r *http.Request, db *sql.DB) (string, string) {
if tripSecure.MatchString(input) {
chunck := tripSecure.FindString(input)
chunck = strings.Replace(chunck, "##", "", 1)
-
- ce := regexp.MustCompile(`(?i)Admin`)
+
+ ce := regexp.MustCompile(`(?i)Admin`)
admin := ce.MatchString(chunck)
board, modcred := GetPasswordFromSession(r)
-
- if(admin && HasAuth(db, modcred, board)) {
+
+ if admin && HasAuth(db, modcred, board) {
return tripSecure.ReplaceAllString(input, ""), "#Admin"
}
hash := TripCodeSecure(chunck)
return tripSecure.ReplaceAllString(input, ""), "!!" + hash
- }
-
+ }
+
trip := regexp.MustCompile("#(.+)?")
if trip.MatchString(input) {
chunck := trip.FindString(input)
chunck = strings.Replace(chunck, "#", "", 1)
-
- ce := regexp.MustCompile(`(?i)Admin`)
+
+ ce := regexp.MustCompile(`(?i)Admin`)
admin := ce.MatchString(chunck)
board, modcred := GetPasswordFromSession(r)
-
- if(admin && HasAuth(db, modcred, board)) {
+
+ if admin && HasAuth(db, modcred, board) {
return trip.ReplaceAllString(input, ""), "#Admin"
}
diff --git a/verification.go b/verification.go
index 555e9ee..67bbf30 100644
--- a/verification.go
+++ b/verification.go
@@ -25,32 +25,31 @@ import (
"strings"
)
-
type Verify struct {
- Type string
+ Type string
Identifier string
- Code string
- Created string
- Board string
+ Code string
+ Created string
+ Board string
}
type VerifyCooldown struct {
Identifier string
- Code string
- Time int
+ Code string
+ Time int
}
type Signature struct {
- KeyId string
- Headers []string
+ KeyId string
+ Headers []string
Signature string
- Algorithm string
+ Algorithm string
}
func DeleteBoardMod(db *sql.DB, verify Verify) {
query := `select code from boardaccess where identifier=$1 and board=$1`
- rows, err := db.Query(query, verify.Identifier, verify.Board)
+ rows, err := db.Query(query, verify.Identifier, verify.Board)
CheckError(err, "could not select code from boardaccess")
@@ -62,25 +61,25 @@ func DeleteBoardMod(db *sql.DB, verify Verify) {
if code != "" {
query := `delete from crossverification where code=$1`
-
+
_, err := db.Exec(query, code)
-
+
CheckError(err, "could not delete code from crossverification")
query = `delete from boardaccess where identifier=$1 and board=$2`
- _, err = db.Exec(query, verify.Identifier, verify.Board)
-
- CheckError(err, "could not delete identifier from boardaccess")
+ _, err = db.Exec(query, verify.Identifier, verify.Board)
+
+ CheckError(err, "could not delete identifier from boardaccess")
}
}
-func GetBoardMod(db *sql.DB, identifier string) Verify{
+func GetBoardMod(db *sql.DB, identifier string) Verify {
var nVerify Verify
query := `select code, board, type, identifier from boardaccess where identifier=$1`
- rows, err := db.Query(query, identifier)
+ rows, err := db.Query(query, identifier)
CheckError(err, "could not select boardaccess query")
@@ -97,14 +96,14 @@ func CreateBoardMod(db *sql.DB, verify Verify) {
query := `select code from verification where identifier=$1 and type=$2`
- rows, err := db.Query(query, verify.Board, verify.Type)
+ rows, err := db.Query(query, verify.Board, verify.Type)
CheckError(err, "could not select verifcaiton query")
defer rows.Close()
var code string
-
+
rows.Next()
rows.Scan(&code)
@@ -112,8 +111,8 @@ func CreateBoardMod(db *sql.DB, verify Verify) {
query := `select identifier from boardaccess where identifier=$1 and board=$2`
- rows, err := db.Query(query, verify.Identifier, verify.Board)
-
+ rows, err := db.Query(query, verify.Identifier, verify.Board)
+
CheckError(err, "could not select idenifier from boardaccess")
defer rows.Close()
@@ -126,14 +125,14 @@ func CreateBoardMod(db *sql.DB, verify Verify) {
query := `insert into crossverification (verificationcode, code) values ($1, $2)`
- _, err := db.Exec(query, code, pass)
-
+ _, err := db.Exec(query, code, pass)
+
CheckError(err, "could not insert new crossverification")
query = `insert into boardaccess (identifier, code, board, type) values ($1, $2, $3, $4)`
_, err = db.Exec(query, verify.Identifier, pass, verify.Board, verify.Type)
-
+
CheckError(err, "could not insert new boardaccess")
fmt.Printf("Board access - Board: %s, Identifier: %s, Code: %s\n", verify.Board, verify.Identifier, pass)
@@ -144,7 +143,7 @@ func CreateBoardMod(db *sql.DB, verify Verify) {
func CreateVerification(db *sql.DB, verify Verify) {
query := `insert into verification (type, identifier, code, created) values ($1, $2, $3, $4)`
- _, err := db.Exec(query, verify.Type, verify.Identifier, verify.Code, time.Now().UTC().Format(time.RFC3339))
+ _, err := db.Exec(query, verify.Type, verify.Identifier, verify.Code, time.Now().UTC().Format(time.RFC3339))
CheckError(err, "error creating verify")
}
@@ -154,20 +153,20 @@ func GetVerificationByEmail(db *sql.DB, email string) Verify {
query := `select type, identifier, code, board from boardaccess where identifier=$1`
- rows, err := db.Query(query, email)
+ rows, err := db.Query(query, email)
defer rows.Close()
- CheckError(err, "error getting verify by email query")
+ CheckError(err, "error getting verify by email query")
defer rows.Close()
for rows.Next() {
err := rows.Scan(&verify.Type, &verify.Identifier, &verify.Code, &verify.Board)
- CheckError(err, "error getting verify by email scan")
+ CheckError(err, "error getting verify by email scan")
}
-
+
return verify
}
@@ -176,7 +175,7 @@ func GetVerificationByCode(db *sql.DB, code string) Verify {
query := `select type, identifier, code, board from boardaccess where code=$1`
- rows, err := db.Query(query, code)
+ rows, err := db.Query(query, code)
defer rows.Close()
@@ -188,9 +187,9 @@ func GetVerificationByCode(db *sql.DB, code string) Verify {
for rows.Next() {
err := rows.Scan(&verify.Type, &verify.Identifier, &verify.Code, &verify.Board)
- CheckError(err, "error getting verify by code scan")
+ CheckError(err, "error getting verify by code scan")
}
-
+
return verify
}
@@ -199,7 +198,7 @@ func GetVerificationCode(db *sql.DB, verify Verify) Verify {
query := `select type, identifier, code, board from boardaccess where identifier=$1 and board=$2`
- rows, err := db.Query(query, verify.Identifier, verify.Board)
+ rows, err := db.Query(query, verify.Identifier, verify.Board)
defer rows.Close()
@@ -211,9 +210,9 @@ func GetVerificationCode(db *sql.DB, verify Verify) Verify {
for rows.Next() {
err := rows.Scan(&nVerify.Type, &nVerify.Identifier, &nVerify.Code, &nVerify.Board)
- CheckError(err, "error getting verify by code scan")
+ CheckError(err, "error getting verify by code scan")
}
-
+
return nVerify
}
@@ -222,29 +221,29 @@ func VerifyCooldownCurrent(db *sql.DB, auth string) VerifyCooldown {
query := `select identifier, code, time from verificationcooldown where code=$1`
- rows, err := db.Query(query, auth)
+ rows, err := db.Query(query, auth)
- defer rows.Close()
+ defer rows.Close()
if err != nil {
query := `select identifier, code, time from verificationcooldown where identifier=$1`
- rows, err := db.Query(query, auth)
+ rows, err := db.Query(query, auth)
defer rows.Close()
-
+
if err != nil {
return current
}
-
+
defer rows.Close()
for rows.Next() {
err = rows.Scan(&current.Identifier, &current.Code, &current.Time)
CheckError(err, "error scanning current verify cooldown verification")
- }
+ }
}
defer rows.Close()
@@ -261,7 +260,7 @@ func VerifyCooldownCurrent(db *sql.DB, auth string) VerifyCooldown {
func VerifyCooldownAdd(db *sql.DB, verify Verify) {
query := `insert into verficationcooldown (identifier, code) values ($1, $2)`
- _, err := db.Exec(query, verify.Identifier, verify.Code)
+ _, err := db.Exec(query, verify.Identifier, verify.Code)
CheckError(err, "error adding verify to cooldown")
}
@@ -272,23 +271,23 @@ func VerficationCooldown(db *sql.DB) {
rows, err := db.Query(query)
- defer rows.Close()
+ defer rows.Close()
CheckError(err, "error with verifiy cooldown query ")
defer rows.Close()
for rows.Next() {
- var verify VerifyCooldown
+ var verify VerifyCooldown
err = rows.Scan(&verify.Identifier, &verify.Code, &verify.Time)
CheckError(err, "error with verifiy cooldown scan ")
- nTime := verify.Time - 1;
+ nTime := verify.Time - 1
query = `update set time=$1 where identifier=$2`
- _, err := db.Exec(query, nTime, verify.Identifier)
+ _, err := db.Exec(query, nTime, verify.Identifier)
CheckError(err, "error with update cooldown query")
@@ -318,11 +317,10 @@ func SendVerification(verify Verify) {
"Subject: Image Board Verification\n\n" +
body
- err := smtp.SendMail(SiteEmailServer + ":" + SiteEmailPort,
+ err := smtp.SendMail(SiteEmailServer+":"+SiteEmailPort,
smtp.PlainAuth("", from, pass, SiteEmailServer),
from, []string{to}, []byte(msg))
-
CheckError(err, "error with smtp")
}
@@ -341,8 +339,8 @@ func IsEmailSetup() bool {
if SiteEmailPort == "" {
return false
- }
-
+ }
+
return true
}
@@ -354,7 +352,7 @@ func HasAuth(db *sql.DB, code string, board string) bool {
return true
}
- return false;
+ return false
}
func HasAuthCooldown(db *sql.DB, auth string) bool {
@@ -363,7 +361,7 @@ func HasAuthCooldown(db *sql.DB, auth string) bool {
return true
}
- fmt.Println("has auth is false")
+ fmt.Println("has auth is false")
return false
}
@@ -377,19 +375,19 @@ func GetVerify(db *sql.DB, access string) Verify {
return verify
}
-func CreateNewCaptcha(db *sql.DB){
- id := RandomID(8)
+func CreateNewCaptcha(db *sql.DB) {
+ id := RandomID(8)
file := "public/" + id + ".png"
-
+
for true {
if _, err := os.Stat("./" + file); err == nil {
- id = RandomID(8)
+ id = RandomID(8)
file = "public/" + id + ".png"
- }else{
+ } else {
break
}
}
-
+
captcha := Captcha()
var pattern string
@@ -398,53 +396,56 @@ func CreateNewCaptcha(db *sql.DB){
srnd := string(rnd)
switch srnd {
- case "0" :
+ case "0":
pattern = "pattern:verticalbricks"
break
- case "1" :
+ case "1":
pattern = "pattern:verticalsaw"
break
-
- case "2" :
+
+ case "2":
pattern = "pattern:hs_cross"
- break
+ break
}
-
+
cmd := exec.Command("convert", "-size", "200x98", pattern, "-transparent", "white", file)
+ cmd.Stderr = os.Stderr
err := cmd.Run()
CheckError(err, "error with captcha first pass")
-
- cmd = exec.Command("convert", file, "-fill", "blue", "-pointsize", "62", "-annotate", "+0+70", captcha, "-tile", "pattern:left30", "-gravity", "center", "-transparent", "white", file)
+
+ cmd = exec.Command("convert", file, "-fill", "blue", "-pointsize", "62", "-annotate", "+0+70", captcha, "-tile", "pattern:left30", "-gravity", "center", "-transparent", "white", file)
+ cmd.Stderr = os.Stderr
err = cmd.Run()
CheckError(err, "error with captcha second pass")
- rnd = fmt.Sprintf("%d", rand.Intn(24) - 12)
-
- cmd = exec.Command("convert", file, "-rotate", rnd, "-wave", "5x35", "-distort", "Arc", "20", "-wave", "2x35", "-transparent", "white", file)
+ rnd = fmt.Sprintf("%d", rand.Intn(24)-12)
+
+ cmd = exec.Command("convert", file, "-rotate", rnd, "-wave", "5x35", "-distort", "Arc", "20", "-wave", "2x35", "-transparent", "white", file)
+ cmd.Stderr = os.Stderr
err = cmd.Run()
- CheckError(err, "error with captcha third pass")
+ CheckError(err, "error with captcha third pass")
var verification Verify
- verification.Type = "captcha"
- verification.Code = captcha
+ verification.Type = "captcha"
+ verification.Code = captcha
verification.Identifier = file
CreateVerification(db, verification)
}
func CreateBoardAccess(db *sql.DB, verify Verify) {
- if(!HasBoardAccess(db, verify)){
- query := `insert into boardaccess (identifier, board) values($1, $2)`
+ if !HasBoardAccess(db, verify) {
+ query := `insert into boardaccess (identifier, board) values($1, $2)`
- _, err := db.Exec(query, verify.Identifier, verify.Board)
+ _, err := db.Exec(query, verify.Identifier, verify.Board)
CheckError(err, "could not instert verification and board into board access")
}
@@ -453,18 +454,18 @@ func CreateBoardAccess(db *sql.DB, verify Verify) {
func HasBoardAccess(db *sql.DB, verify Verify) bool {
query := `select count(*) from boardaccess where identifier=$1 and board=$2`
- rows, err := db.Query(query, verify.Identifier, verify.Board)
+ rows, err := db.Query(query, verify.Identifier, verify.Board)
- defer rows.Close()
+ defer rows.Close()
- CheckError(err, "could not select boardaccess based on verify")
+ CheckError(err, "could not select boardaccess based on verify")
var count int
rows.Next()
rows.Scan(&count)
- if(count > 0) {
+ if count > 0 {
return true
} else {
return false
@@ -475,11 +476,11 @@ func BoardHasAuthType(db *sql.DB, board string, auth string) bool {
authTypes := GetActorAuth(db, board)
for _, e := range authTypes {
- if(e == auth){
+ if e == auth {
return true
}
}
-
+
return false
}
@@ -491,39 +492,39 @@ func Captcha() string {
for i := 0; i < rng; i++ {
newID += string(domain[rand.Intn(len(domain))])
}
-
+
return newID
-}
+}
func CreatePem(db *sql.DB, actor Actor) {
privatekey, err := rsa.GenerateKey(crand.Reader, 2048)
CheckError(err, "error creating private pem key")
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privatekey)
-
+
privateKeyBlock := &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privateKeyBytes,
}
-
+
privatePem, err := os.Create("./pem/board/" + actor.Name + "-private.pem")
- CheckError(err, "error creating private pem file for " + actor.Name)
-
+ CheckError(err, "error creating private pem file for "+actor.Name)
+
err = pem.Encode(privatePem, privateKeyBlock)
CheckError(err, "error encoding private pem")
publickey := &privatekey.PublicKey
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publickey)
- CheckError(err, "error Marshaling public key to X509")
-
+ CheckError(err, "error Marshaling public key to X509")
+
publicKeyBlock := &pem.Block{
Type: "PUBLIC KEY",
Bytes: publicKeyBytes,
}
-
+
publicPem, err := os.Create("./pem/board/" + actor.Name + "-public.pem")
- CheckError(err, "error creating public pem file for " + actor.Name)
-
+ CheckError(err, "error creating public pem file for "+actor.Name)
+
err = pem.Encode(publicPem, publicKeyBlock)
CheckError(err, "error encoding public pem")
@@ -534,13 +535,13 @@ func CreatePem(db *sql.DB, actor Actor) {
StorePemToDB(db, actor)
}
- fmt.Println(`Created PEM keypair for the "` + actor.Name +`" board. Please keep in mind that
+ fmt.Println(`Created PEM keypair for the "` + actor.Name + `" board. Please keep in mind that
the PEM key is crucial in identifying yourself as the legitimate owner of the board,
-so DO NOT LOSE IT!!! If you lose it, YOU WILL LOSE ACCESS TO YOUR BOARD!`);
+so DO NOT LOSE IT!!! If you lose it, YOU WILL LOSE ACCESS TO YOUR BOARD!`)
}
-func CreatePublicKeyFromPrivate(db *sql.DB, actor *Actor, publicKeyPem string) error{
- publicFilename := GetActorPemFileFromDB(db, publicKeyPem);
+func CreatePublicKeyFromPrivate(db *sql.DB, actor *Actor, publicKeyPem string) error {
+ publicFilename := GetActorPemFileFromDB(db, publicKeyPem)
privateFilename := strings.ReplaceAll(publicFilename, "public.pem", "private.pem")
_, err := os.Stat(privateFilename)
if err == nil {
@@ -551,7 +552,7 @@ func CreatePublicKeyFromPrivate(db *sql.DB, actor *Actor, publicKeyPem string) e
if block == nil || block.Type != "RSA PRIVATE KEY" {
return errors.New("failed to decode PEM block containing public key")
}
-
+
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
CheckError(err, "failed to parse private key")
@@ -562,13 +563,13 @@ func CreatePublicKeyFromPrivate(db *sql.DB, actor *Actor, publicKeyPem string) e
Headers: nil,
Bytes: publicKeyDer,
}
-
+
publicFileWriter, err := os.Create(publicFilename)
- CheckError(err, "error creating public pem file for " + actor.Name)
+ CheckError(err, "error creating public pem file for "+actor.Name)
err = pem.Encode(publicFileWriter, &pubKeyBlock)
CheckError(err, "error encoding public pem")
- }else{
+ } else {
fmt.Println(`\nUnable to locate private key from public key generation. Now,
this means that you are now missing the proof that you are the
owner of the "` + actor.Name + `" board. If you are the developer,
@@ -584,15 +585,15 @@ accepting your posts from your board from this site. Good luck ;)`)
func StorePemToDB(db *sql.DB, actor Actor) {
query := "select publicKeyPem from actor where id=$1"
rows, err := db.Query(query, actor.Id)
-
- CheckError(err, "error selecting publicKeyPem id from actor")
+
+ CheckError(err, "error selecting publicKeyPem id from actor")
var result string
defer rows.Close()
rows.Next()
rows.Scan(&result)
- if(result != "") {
+ if result != "" {
return
}
@@ -619,22 +620,22 @@ func ActivitySign(db *sql.DB, actor Actor, signature string) (string, error) {
rows.Next()
rows.Scan(&file)
- file = strings.ReplaceAll(file, "public.pem", "private.pem")
+ file = strings.ReplaceAll(file, "public.pem", "private.pem")
_, err = os.Stat(file)
if err == nil {
- publickey, err:= ioutil.ReadFile(file)
+ publickey, err := ioutil.ReadFile(file)
CheckError(err, "error reading file")
block, _ := pem.Decode(publickey)
pub, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
- rng :=crand.Reader
+ rng := crand.Reader
hashed := sha256.New()
- hashed.Write([]byte(signature))
+ hashed.Write([]byte(signature))
cipher, _ := rsa.SignPKCS1v15(rng, pub, crypto.SHA256, hashed.Sum(nil))
return base64.StdEncoding.EncodeToString(cipher), nil
- }else{
+ } else {
fmt.Println(`\n Unable to locate private key. Now,
this means that you are now missing the proof that you are the
owner of the "` + actor.Name + `" board. If you are the developer,
@@ -666,22 +667,21 @@ func ActivityVerify(actor Actor, signature string, verify string) error {
func VerifyHeaderSignature(r *http.Request, actor Actor) bool {
s := ParseHeaderSignature(r.Header.Get("Signature"))
- var method string
- var path string
- var host string
- var date string
- var digest string
- var contentLength string
+ var method string
+ var path string
+ var host string
+ var date string
+ var digest string
+ var contentLength string
var sig string
for i, e := range s.Headers {
var nl string
- if i < len(s.Headers) - 1 {
+ if i < len(s.Headers)-1 {
nl = "\n"
}
-
-
+
if e == "(request-target)" {
method = strings.ToLower(r.Method)
path = r.URL.Path
@@ -709,9 +709,9 @@ func VerifyHeaderSignature(r *http.Request, actor Actor) bool {
if e == "content-length" {
contentLength = r.Header.Get("content-length")
- sig += "content-length: " + contentLength + "" + nl
+ sig += "content-length: " + contentLength + "" + nl
continue
- }
+ }
}
if s.KeyId != actor.PublicKey.Id {
@@ -720,14 +720,14 @@ func VerifyHeaderSignature(r *http.Request, actor Actor) bool {
t, _ := time.Parse(time.RFC1123, date)
- if(time.Now().UTC().Sub(t).Seconds() > 75) {
+ if time.Now().UTC().Sub(t).Seconds() > 75 {
return false
}
-
+
if ActivityVerify(actor, s.Signature, sig) != nil {
return false
}
-
+
return true
}
@@ -737,7 +737,7 @@ func ParseHeaderSignature(signature string) Signature {
keyId := regexp.MustCompile(`keyId=`)
headers := regexp.MustCompile(`headers=`)
sig := regexp.MustCompile(`signature=`)
- algo := regexp.MustCompile(`algorithm=`)
+ algo := regexp.MustCompile(`algorithm=`)
signature = strings.ReplaceAll(signature, "\"", "")
parts := strings.Split(signature, ",")
@@ -747,7 +747,7 @@ func ParseHeaderSignature(signature string) Signature {
nsig.KeyId = keyId.ReplaceAllString(e, "")
continue
}
-
+
if headers.MatchString(e) {
header := headers.ReplaceAllString(e, "")
nsig.Headers = strings.Split(header, " ")
@@ -762,7 +762,7 @@ func ParseHeaderSignature(signature string) Signature {
if algo.MatchString(e) {
nsig.Algorithm = algo.ReplaceAllString(e, "")
continue
- }
+ }
}
return nsig
diff --git a/webfinger.go b/webfinger.go
index 004bdca..c8fd0ae 100644
--- a/webfinger.go
+++ b/webfinger.go
@@ -1,12 +1,12 @@
package main
type Webfinger struct {
- Subject string `json:"subject,omitempty"`
- Links []WebfingerLink `json:"links,omitempty"`
+ Subject string `json:"subject,omitempty"`
+ Links []WebfingerLink `json:"links,omitempty"`
}
type WebfingerLink struct {
- Rel string `json:"rel,omitempty"`
+ Rel string `json:"rel,omitempty"`
Type string `json:"type,omitempty"`
- Href string `json:"href,omitempty"`
+ Href string `json:"href,omitempty"`
}