aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFChannel <>2021-06-28 08:18:14 -0700
committerFChannel <>2021-06-28 08:18:14 -0700
commit34fa3d38b97ff08130e8709c4820915dfd684b19 (patch)
tree36e0f9cf2d58ab6b915b29bda515ac662dbcae55
parente3eba9b6c64b1e8d884a51638deca0078f2f4760 (diff)
parentea9b7878dcbcedce3669eb2df23a14546f7188af (diff)
Merge with master
-rw-r--r--.gitignore4
-rw-r--r--README.md47
-rw-r--r--accept.go19
-rw-r--r--activityPubStruct.go16
-rw-r--r--cacheDatabase.go (renamed from CacheDatabase.go)108
-rw-r--r--client.go133
-rw-r--r--database.go (renamed from Database.go)494
-rw-r--r--databaseschema.psql16
-rw-r--r--follow.go (renamed from Follow.go)39
-rw-r--r--main.go808
-rw-r--r--outboxGet.go8
-rw-r--r--outboxPost.go (renamed from OutboxPost.go)159
-rw-r--r--static/bottom.html7
-rw-r--r--static/faq.html2
-rw-r--r--static/index.html15
-rw-r--r--static/js/footerscript.js4
-rw-r--r--static/js/posts.js66
-rw-r--r--static/main.html20
-rw-r--r--static/manage.html49
-rw-r--r--static/nadmin.html37
-rw-r--r--static/ncatalog.html47
-rw-r--r--static/notfound.pngbin0 -> 96599 bytes
-rw-r--r--static/npost.html17
-rw-r--r--static/nposts.html11
-rw-r--r--static/posts.html66
-rw-r--r--static/sensitive.pngbin0 -> 3737 bytes
-rw-r--r--static/top.html1
-rw-r--r--verification.go238
-rw-r--r--webfinger.go12
29 files changed, 1664 insertions, 779 deletions
diff --git a/.gitignore b/.gitignore
index 1f7a89d..adb71bb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,7 @@
+.*
*~
#*
public/
config
-clientkey \ No newline at end of file
+clientkey
+pem/
diff --git a/README.md b/README.md
index 9279fe9..2f6740d 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@ FChannel is a [libre](https://en.wikipedia.org/wiki/Free_and_open-source_softwar
There are currently two instances federated with each other: https://fchan.xyz and https://0x00000000.xyz
There is an anon testing FChannel instances on TOR/Loki/I2P. Find more information here: https://fchan.xyz/g/MORL0KUT
-It is a testing envirmoent, so the instances might come and go.
+It is a testing environment, so the instances might come and go.
## To Do List
Current things that will be implemented first are:
@@ -20,11 +20,15 @@ Any contributions or suggestions are appreciated. Best way to give immediate fee
## Minimum Server Requirements
-- golang v1.11+
+- Go v1.16+
-- postgresql
+- PostgreSQL
-- redis
+- Redis
+
+- ImageMagick
+
+- exiv2
## Server Installation Instructions
@@ -34,9 +38,7 @@ Any contributions or suggestions are appreciated. Best way to give immediate fee
- Create the database, user name, and password for psql that is used in config file.
-- Run `psql -U (user) -d (database) -f databaseschema.psql`
-
-- Finally start the server with `go run`.
+- Start the server with `go run`.
## Server Configuration
@@ -74,29 +76,18 @@ Any contributions or suggestions are appreciated. Best way to give immediate fee
`emailpass:password`
+### local testing
-### Creating a new board
+ When testing on a local env when setting the `instance` value in the config file you have to append the port number to the local address eg. `instance:localhost:3000` with `instanceport` also being set to the same port.
- `CreateNewBoardDB(db *sql.DB, actor Actor)`
-
- returns Actor.
-
-### Creating a new actor
-
- `CreateNewActor(board string, prefName string, summary string, authReq []string, restricted bool)`
-
- returns Actor
-
- - `board` is the abbreviated name such as `g`
-
- - `prefName` is the fully readable name such as `Technology`
-
- - `summary` is a summary of the board
-
- - `authReq` is an array string of required privileges to post on the board, default is: `[]string{"captcha","email","passphrase"}`
-
- - `restricted` is bool. `true` is blue board, `false` is red board
-
+ If you want to test federation between servers locally you have to use your local ip as the `instance` eg. `instance:192.168.0.2:3000` and `instance:192:168:0:3:3000` adding the port to localhost will not route correctly.
+
+### Managing the server
+
+ To access the managment page to create new boards or subscribe to other boards, when you start the server the console will output the `Mod key` and `Admin Login`
+ Use the `Mod key` by appending it to your servers url, `https://fchan.xyz/[Mod key]` once there you will be prompted for the `Admin Login` credentials.
+ You can manage each board by appending the `Mod key` to the desired board url: `https://fchan.xyz/[Mod Key]/g`
+ The `Mod key` is not static and is reset on server restart.
## Server Update
diff --git a/accept.go b/accept.go
new file mode 100644
index 0000000..1263ab8
--- /dev/null
+++ b/accept.go
@@ -0,0 +1,19 @@
+package main
+
+import "strings"
+import "regexp"
+
+// False positive for application/ld+ld, application/activity+ld, application/json+json
+var activityRegexp = regexp.MustCompile("application\\/(ld|json|activity)((\\+(ld|json))|$)")
+
+func acceptActivity(header string) bool {
+ accept := false
+ 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\"")
+ } else {
+ accept = accept || activityRegexp.MatchString(header)
+ }
+ return accept
+}
diff --git a/activityPubStruct.go b/activityPubStruct.go
index 9fe66d6..06c97e1 100644
--- a/activityPubStruct.go
+++ b/activityPubStruct.go
@@ -74,11 +74,18 @@ type Actor struct {
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"`
+}
+
type Activity struct {
AtContext
Type string `json:"type,omitempty"`
@@ -101,7 +108,8 @@ type ObjectBase struct {
Option []string `json:"option,omitempty"`
Alias string `json:"alias,omitempty"`
AttributedTo string `json:"attributedTo,omitempty"`
- Actor *Actor `json:"actor,omitempty"`
+ TripCode string `json:"tripcode,omitempty"`
+ Actor string `json:"actor,omitempty"`
Audience string `json:"audience,omitempty"`
Content string `json:"content,omitempty"`
EndTime string `json:"endTime,omitempty"`
@@ -129,7 +137,8 @@ type ObjectBase struct {
Bcc string `json:"Bcc,omitempty"`
MediaType string `json:"mediatype,omitempty"`
Duration string `json:"duration,omitempty"`
- Size int64 `json:"size,omitempty"`
+ Size int64 `json:"size,omitempty"`
+ Sensitive bool `json:"sensitive"`
}
type CryptoCur struct {
@@ -144,7 +153,8 @@ type NestedObjectBase struct {
Name string `json:"name,omitempty"`
Alias string `json:"alias,omitempty"`
AttributedTo string `json:"attributedTo,omitempty"`
- Actor *Actor `json:"actor,omitempty"`
+ TripCode string `json:"tripcode,omitempty"`
+ Actor string `json:"actor,omitempty"`
Audience string `json:"audience,omitempty"`
Content string `json:"content,omitempty"`
EndTime string `json:"endTime,omitempty"`
diff --git a/CacheDatabase.go b/cacheDatabase.go
index ec3c409..5acead7 100644
--- a/CacheDatabase.go
+++ b/cacheDatabase.go
@@ -1,7 +1,6 @@
package main
import "fmt"
-import "time"
import "database/sql"
import _ "github.com/lib/pq"
@@ -31,15 +30,34 @@ func WriteObjectToCache(db *sql.DB, obj ObjectBase) ObjectBase {
return obj
}
-func WriteObjectUpdatesToCache(db *sql.DB, obj ObjectBase) {
- query := `update cacheactivitystream set updated=$1 where id=$2`
-
- _, e := db.Exec(query, time.Now().Format(time.RFC3339), obj.Id)
-
- if e != nil{
- fmt.Println("error inserting updating inreplyto")
- panic(e)
- }
+func WriteActorObjectToCache(db *sql.DB, obj ObjectBase) ObjectBase {
+ 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)
+ }
+
+ } else {
+ WriteActivitytoCache(db, obj)
+ }
+
+ WriteActorObjectReplyToDB(db, obj)
+
+ if obj.Replies != nil {
+ for _, e := range obj.Replies.OrderedItems {
+ WriteActorObjectToCache(db, e)
+ }
+ }
+
+ return obj
}
func WriteActivitytoCache(db *sql.DB, obj ObjectBase) {
@@ -63,9 +81,9 @@ func WriteActivitytoCache(db *sql.DB, obj ObjectBase) {
return
}
- query = `insert into cacheactivitystream (id, type, name, content, published, updated, attributedto, actor) values ($1, $2, $3, $4, $5, $6, $7, $8)`
+ 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.Published, obj.AttributedTo, obj.Actor.Id)
+ _, e := db.Exec(query, obj.Id ,obj.Type, obj.Name, obj.Content, obj.Published, obj.Published, obj.AttributedTo, obj.Actor, obj.TripCode, obj.Sensitive)
if e != nil{
fmt.Println("error inserting new activity cache")
@@ -94,9 +112,9 @@ func WriteActivitytoCacheWithAttachment(db *sql.DB, obj ObjectBase, attachment O
return
}
- query = `insert into cacheactivitystream (id, type, name, content, attachment, preview, published, updated, attributedto, actor) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`
+ 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.Published, obj.AttributedTo, obj.Actor.Id)
+ _, e := db.Exec(query, obj.Id ,obj.Type, obj.Name, obj.Content, attachment.Id, preview.Id, obj.Published, obj.Published, obj.AttributedTo, obj.Actor, obj.TripCode, obj.Sensitive)
if e != nil{
fmt.Println("error inserting new activity with attachment cache")
@@ -244,40 +262,10 @@ func WriteActorToCache(db *sql.DB, actorID string) {
collection := GetActorCollection(actor.Outbox)
for _, e := range collection.OrderedItems {
- WriteObjectToCache(db, e)
+ WriteActorObjectToCache(db, e)
}
}
-func DeleteObjectFromCache(db *sql.DB, id string) {
- query := `select attachment, preview from cacheactivitystream where id=$1 `
-
- rows, err := db.Query(query, id)
- CheckError(err, "could not select cache activitystream")
-
- var attachment string
- var preview string
-
- defer rows.Close()
- rows.Next()
- rows.Scan(&attachment, &preview)
-
- query = `delete from cacheactivitystream where id=$1`
- _, err = db.Exec(query, attachment)
- CheckError(err, "could not delete attachmet cache activitystream")
-
- query = `delete from cacheactivitystream where id=$1`
- _, err = db.Exec(query, preview)
- CheckError(err, "could not delete preview cache activitystream")
-
- query = `delete from cacheactivitystream where id=$1`
- _, err = db.Exec(query, id)
- CheckError(err, "could not delete object cache activitystream")
-
- query = `delete from replies where id=$1`
- _, err = db.Exec(query, id)
- CheckError(err, "could not delete cache replies activitystream")
-}
-
func DeleteActorCache(db *sql.DB, actorID string) {
query := `select id from cacheactivitystream where id in (select id from cacheactivitystream where actor=$1)`
@@ -291,34 +279,6 @@ func DeleteActorCache(db *sql.DB, actorID string) {
var id string
rows.Scan(&id)
- DeleteObjectFromCache(db, id)
+ DeleteObject(db, id)
}
}
-
-func TombstoneObjectFromCache(db *sql.DB, id string) {
-
- datetime := time.Now().Format(time.RFC3339)
-
- query := `update cacheactivitystream set type='Tombstone', name='', content='', attributedto='deleted', updated=$1, deleted=$2 where id=$3`
-
- _, err := db.Exec(query, datetime, datetime, id)
-
- CheckError(err, "error with tombstone cache object")
-
- query = `update cacheactivitystream set type='Tombstone', mediatype='image/png', href=$1, name='', content='', attributedto='deleted', updated=$2, deleted=$3 where id in (select attachment from cacheactivitystream where id=$4)`
-
- _, err = db.Exec(query, "/public/removed.png", datetime, datetime, id)
-
- CheckError(err, "error with tombstone attachment cache object")
-
- query = `update cacheactivitystream set type='Tombstone', mediatype='image/png', href=$1, name='', content='', attributedto='deleted', updated=$2, deleted=$3 where id in (select preview from cacheactivitystream where id=$4)`
-
- _, err = db.Exec(query, "/public/removed.png", datetime, datetime, id)
-
- CheckError(err, "error with tombstone preview cache object")
-
- query = `delete from replies where id=$1`
- _, err = db.Exec(query, id)
-
- CheckError(err, "could not delete cache replies activitystream")
-}
diff --git a/client.go b/client.go
index d31ba07..91f5f1f 100644
--- a/client.go
+++ b/client.go
@@ -47,6 +47,8 @@ type PageData struct {
Boards []Board
Posts []ObjectBase
Key string
+ PostId string
+ Instance Actor
}
type AdminPage struct {
@@ -77,7 +79,7 @@ func IndexGet(w http.ResponseWriter, r *http.Request, db *sql.DB) {
t := template.Must(template.ParseFiles("./static/main.html", "./static/index.html"))
actor := GetActorFromDB(db, Domain)
-
+
var data PageData
data.Title = "Welcome to " + actor.PreferredUsername
data.Message = fmt.Sprintf("%s is a federated image board based on activitypub. The current version of the code running the server is still a work in progress, expect a bumpy ride for the time being. Get the server code here https://github.com/FChannel0", Domain)
@@ -87,7 +89,8 @@ func IndexGet(w http.ResponseWriter, r *http.Request, db *sql.DB) {
data.Board.Domain = Domain
data.Board.ModCred, _ = GetPasswordFromSession(r)
data.Board.Actor = actor
- data.Board.Post.Actor = &actor
+ data.Board.Post.Actor = actor.Id
+ data.Board.Restricted = actor.Restricted
t.ExecuteTemplate(w, "layout", data)
}
@@ -115,7 +118,7 @@ func OutboxGet(w http.ResponseWriter, r *http.Request, db *sql.DB, collection Co
returnData.Board.Restricted = actor.Restricted
returnData.CurrentPage = page
- returnData.Board.Post.Actor = actor
+ returnData.Board.Post.Actor = actor.Id
returnData.Board.Captcha = Domain + "/" + GetRandomCaptcha(db)
returnData.Board.CaptchaCode = GetCaptchaCode(returnData.Board.Captcha)
@@ -124,10 +127,6 @@ func OutboxGet(w http.ResponseWriter, r *http.Request, db *sql.DB, collection Co
returnData.Key = *Key
- DeleteRemovedPosts(db, &collection)
- DeleteTombstoneReplies(&collection)
- DeleteTombstonePosts(&collection)
-
returnData.Boards = Boards
returnData.Posts = collection.OrderedItems
@@ -149,7 +148,7 @@ func CatalogGet(w http.ResponseWriter, r *http.Request, db *sql.DB, collection C
t := template.Must(template.ParseFiles("./static/main.html", "./static/ncatalog.html", "./static/top.html"))
actor := collection.Actor
-
+
var returnData PageData
returnData.Board.Name = actor.Name
returnData.Board.PrefName = actor.PreferredUsername
@@ -162,8 +161,10 @@ func CatalogGet(w http.ResponseWriter, r *http.Request, db *sql.DB, collection C
returnData.Board.Restricted = actor.Restricted
returnData.Key = *Key
- returnData.Board.Post.Actor = actor
+ returnData.Board.Post.Actor = actor.Id
+ returnData.Instance = GetActorFromDB(db, Domain)
+
returnData.Board.Captcha = Domain + "/" + GetRandomCaptcha(db)
returnData.Board.CaptchaCode = GetCaptchaCode(returnData.Board.Captcha)
@@ -171,9 +172,6 @@ func CatalogGet(w http.ResponseWriter, r *http.Request, db *sql.DB, collection C
returnData.Boards = Boards
- DeleteRemovedPosts(db, &collection)
- DeleteTombstonePosts(&collection)
-
returnData.Posts = collection.OrderedItems
t.ExecuteTemplate(w, "layout", returnData)
@@ -191,7 +189,6 @@ func PostGet(w http.ResponseWriter, r *http.Request, db *sql.DB){
inReplyTo := actor.Id + "/" + postId
var returnData PageData
-
returnData.Board.Name = actor.Name
returnData.Board.PrefName = actor.PreferredUsername
returnData.Board.To = actor.Outbox
@@ -204,6 +201,8 @@ func PostGet(w http.ResponseWriter, r *http.Request, db *sql.DB){
returnData.Board.Captcha = Domain + "/" + GetRandomCaptcha(db)
returnData.Board.CaptchaCode = GetCaptchaCode(returnData.Board.Captcha)
+ returnData.Instance = GetActorFromDB(db, Domain)
+
returnData.Title = "/" + returnData.Board.Name + "/ - " + returnData.Board.PrefName
returnData.Key = *Key
@@ -216,30 +215,29 @@ func PostGet(w http.ResponseWriter, r *http.Request, db *sql.DB){
name := GetActorFollowNameFromPath(path)
followActors := GetActorsFollowFromName(actor, name)
followCollection := GetActorsFollowPostFromId(db, followActors, postId)
-
- returnData.Board.Post.Actor = followCollection.Actor
-
- DeleteRemovedPosts(db, &followCollection)
- DeleteTombstoneReplies(&followCollection)
- if len(followCollection.OrderedItems) > 0 {
+ if len(followCollection.OrderedItems) > 0 {
returnData.Board.InReplyTo = followCollection.OrderedItems[0].Id
returnData.Posts = append(returnData.Posts, followCollection.OrderedItems[0])
+ var actor Actor
+ actor = FingerActor(returnData.Board.InReplyTo)
+ returnData.Board.Post.Actor = actor.Id
}
} else {
collection := GetObjectByIDFromDB(db, inReplyTo)
- returnData.Board.Post.Actor = collection.Actor
+ returnData.Board.Post.Actor = collection.Actor.Id
returnData.Board.InReplyTo = inReplyTo
- DeleteRemovedPosts(db, &collection)
- DeleteTombstoneReplies(&collection)
-
if len(collection.OrderedItems) > 0 {
returnData.Posts = append(returnData.Posts, collection.OrderedItems[0])
}
}
+ if len(returnData.Posts) > 0 {
+ returnData.PostId = shortURL(returnData.Board.To, returnData.Posts[0].Id)
+ }
+
t.ExecuteTemplate(w, "layout", returnData)
}
@@ -345,95 +343,6 @@ func GetCaptchaCode(captcha string) string {
return code
}
-func DeleteTombstoneReplies(collection *Collection) {
-
- for i, e := range collection.OrderedItems {
- var replies CollectionBase
- for _, k := range e.Replies.OrderedItems {
- if k.Type != "Tombstone" {
- replies.OrderedItems = append(replies.OrderedItems, k)
- }
- }
-
- replies.TotalItems = collection.OrderedItems[i].Replies.TotalItems
- replies.TotalImgs = collection.OrderedItems[i].Replies.TotalImgs
- collection.OrderedItems[i].Replies = &replies
- }
-}
-
-func DeleteTombstonePosts(collection *Collection) {
- var nColl Collection
-
- for _, e := range collection.OrderedItems {
- if e.Type != "Tombstone" {
- nColl.OrderedItems = append(nColl.OrderedItems, e)
- }
- }
- collection.OrderedItems = nColl.OrderedItems
-}
-
-func DeleteRemovedPosts(db *sql.DB, collection *Collection) {
-
- removed := GetLocalDeleteDB(db)
-
- for p, e := range collection.OrderedItems {
- for _, j := range removed {
- if e.Id == j.ID {
- if j.Type == "attachment" {
- collection.OrderedItems[p].Preview.Href = "/public/removed.png"
- collection.OrderedItems[p].Preview.Name = "deleted"
- collection.OrderedItems[p].Preview.MediaType = "image/png"
- collection.OrderedItems[p].Attachment[0].Href = "/public/removed.png"
- collection.OrderedItems[p].Attachment[0].Name = "deleted"
- collection.OrderedItems[p].Attachment[0].MediaType = "image/png"
- } else {
- collection.OrderedItems[p].AttributedTo = "deleted"
- collection.OrderedItems[p].Content = ""
- collection.OrderedItems[p].Type = "Tombstone"
- if collection.OrderedItems[p].Attachment != nil {
- collection.OrderedItems[p].Preview.Href = "/public/removed.png"
- collection.OrderedItems[p].Preview.Name = "deleted"
- collection.OrderedItems[p].Preview.MediaType = "image/png"
- collection.OrderedItems[p].Attachment[0].Href = "/public/removed.png"
- collection.OrderedItems[p].Attachment[0].Name = "deleted"
- collection.OrderedItems[p].Attachment[0].MediaType = "image/png"
- }
- }
- }
- }
-
- for i, r := range e.Replies.OrderedItems {
- for _, k := range removed {
- if r.Id == k.ID {
- if k.Type == "attachment" {
- e.Replies.OrderedItems[i].Preview.Href = "/public/removed.png"
- e.Replies.OrderedItems[i].Preview.Name = "deleted"
- e.Replies.OrderedItems[i].Preview.MediaType = "image/png"
- e.Replies.OrderedItems[i].Attachment[0].Href = "/public/removed.png"
- e.Replies.OrderedItems[i].Attachment[0].Name = "deleted"
- e.Replies.OrderedItems[i].Attachment[0].MediaType = "image/png"
- collection.OrderedItems[p].Replies.TotalImgs = collection.OrderedItems[p].Replies.TotalImgs - 1
- } else {
- e.Replies.OrderedItems[i].AttributedTo = "deleted"
- e.Replies.OrderedItems[i].Content = ""
- e.Replies.OrderedItems[i].Type = "Tombstone"
- if e.Replies.OrderedItems[i].Attachment != nil {
- e.Replies.OrderedItems[i].Preview.Href = "/public/removed.png"
- e.Replies.OrderedItems[i].Preview.Name = "deleted"
- e.Replies.OrderedItems[i].Preview.MediaType = "image/png"
- e.Replies.OrderedItems[i].Attachment[0].Name = "deleted"
- e.Replies.OrderedItems[i].Attachment[0].Href = "/public/removed.png"
- e.Replies.OrderedItems[i].Attachment[0].MediaType = "image/png"
- collection.OrderedItems[p].Replies.TotalImgs = collection.OrderedItems[p].Replies.TotalImgs - 1
- }
- collection.OrderedItems[p].Replies.TotalItems = collection.OrderedItems[p].Replies.TotalItems - 1
- }
- }
- }
- }
- }
-}
-
func CreateLocalDeleteDB(db *sql.DB, id string, _type string) {
query := `select id from removed where id=$1`
diff --git a/Database.go b/database.go
index 50dc2cb..e88dd1e 100644
--- a/Database.go
+++ b/database.go
@@ -6,13 +6,12 @@ import _ "github.com/lib/pq"
import "time"
import "os"
import "strings"
-// import "regexp"
import "sort"
func GetActorFromDB(db *sql.DB, id string) Actor {
var nActor Actor
- query :=`select type, id, name, preferedusername, inbox, outbox, following, followers, restricted, summary 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)
@@ -20,19 +19,22 @@ func GetActorFromDB(db *sql.DB, id string) Actor {
return nActor
}
- defer rows.Close()
+ var publicKeyPem string
+ defer rows.Close()
for rows.Next() {
- err = rows.Scan(&nActor.Type, &nActor.Id, &nActor.Name, &nActor.PreferredUsername, &nActor.Inbox, &nActor.Outbox, &nActor.Following, &nActor.Followers, &nActor.Restricted, &nActor.Summary)
+ err = rows.Scan(&nActor.Type, &nActor.Id, &nActor.Name, &nActor.PreferredUsername, &nActor.Inbox, &nActor.Outbox, &nActor.Following, &nActor.Followers, &nActor.Restricted, &nActor.Summary, &publicKeyPem)
CheckError(err, "error with actor from db scan ")
}
+ nActor.PublicKey = GetActorPemFromDB(db, publicKeyPem)
+
return nActor
}
func GetActorByNameFromDB(db *sql.DB, name string) Actor {
var nActor Actor
- query :=`select type, id, name, preferedusername, inbox, outbox, following, followers, restricted, summary 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)
@@ -40,12 +42,15 @@ func GetActorByNameFromDB(db *sql.DB, name string) Actor {
return nActor
}
+ var publicKeyPem string
defer rows.Close()
for rows.Next() {
- err = rows.Scan(&nActor.Type, &nActor.Id, &nActor.Name, &nActor.PreferredUsername, &nActor.Inbox, &nActor.Outbox, &nActor.Following, &nActor.Followers, &nActor.Restricted, &nActor.Summary)
+ err = rows.Scan(&nActor.Type, &nActor.Id, &nActor.Name, &nActor.PreferredUsername, &nActor.Inbox, &nActor.Outbox, &nActor.Following, &nActor.Followers, &nActor.Restricted, &nActor.Summary, &publicKeyPem)
CheckError(err, "error with actor from db scan ")
}
+ nActor.PublicKey = GetActorPemFromDB(db, publicKeyPem)
+
return nActor
}
@@ -99,27 +104,29 @@ func CreateNewBoardDB(db *sql.DB, actor Actor) Actor{
nverify.Board = actor.Id
nverify.Identifier = "post"
nverify.Type = "post"
- CreateBoardMod(db, nverify)
+ CreateBoardMod(db, nverify)
+ CreatePem(db, actor)
+
if actor.Name != "main" {
- var nActor Actor
var nObject ObjectBase
var nActivity Activity
+ nActor := GetActorFromDB(db, Domain)
nActivity.AtContext.Context = "https://www.w3.org/ns/activitystreams"
nActivity.Type = "Follow"
nActivity.Actor = &nActor
nActivity.Object = &nObject
- nActivity.Actor.Id = Domain
- var mActor Actor
- nActivity.Object.Actor = &mActor
- nActivity.Object.Actor.Id = actor.Id
+
+ mActor := GetActorFromDB(db, actor.Id)
+ nActivity.Object.Actor = mActor.Id
nActivity.To = append(nActivity.To, actor.Id)
response := AcceptFollow(nActivity)
SetActorFollowingDB(db, response)
MakeActivityRequest(db, nActivity)
}
+
}
return actor
@@ -152,10 +159,10 @@ func GetBoards(db *sql.DB) []Actor {
}
func WriteObjectToDB(db *sql.DB, obj ObjectBase) ObjectBase {
- obj.Id = fmt.Sprintf("%s/%s", obj.Actor.Id, CreateUniqueID(db, obj.Actor.Id))
+ obj.Id = fmt.Sprintf("%s/%s", obj.Actor, CreateUniqueID(db, obj.Actor))
if len(obj.Attachment) > 0 {
if obj.Preview.Href != "" {
- obj.Preview.Id = fmt.Sprintf("%s/%s", obj.Actor.Id, CreateUniqueID(db, obj.Actor.Id))
+ obj.Preview.Id = fmt.Sprintf("%s/%s", obj.Actor, CreateUniqueID(db, obj.Actor))
obj.Preview.Published = time.Now().Format(time.RFC3339)
obj.Preview.Updated = time.Now().Format(time.RFC3339)
obj.Preview.AttributedTo = obj.Id
@@ -163,7 +170,7 @@ func WriteObjectToDB(db *sql.DB, obj ObjectBase) ObjectBase {
}
for i, _ := range obj.Attachment {
- obj.Attachment[i].Id = fmt.Sprintf("%s/%s", obj.Actor.Id, CreateUniqueID(db, obj.Actor.Id))
+ obj.Attachment[i].Id = fmt.Sprintf("%s/%s", obj.Actor, CreateUniqueID(db, obj.Actor))
obj.Attachment[i].Published = time.Now().Format(time.RFC3339)
obj.Attachment[i].Updated = time.Now().Format(time.RFC3339)
obj.Attachment[i].AttributedTo = obj.Id
@@ -189,7 +196,16 @@ func WriteObjectUpdatesToDB(db *sql.DB, obj ObjectBase) {
if e != nil{
fmt.Println("error inserting updating inreplyto")
panic(e)
- }
+ }
+
+ query = `update cacheactivitystream set updated=$1 where id=$2`
+
+ _, e = db.Exec(query, time.Now().Format(time.RFC3339), obj.Id)
+
+ if e != nil{
+ fmt.Println("error inserting updating cache inreplyto")
+ panic(e)
+ }
}
func WriteObjectReplyToLocalDB(db *sql.DB, id string, replyto string) {
@@ -253,11 +269,7 @@ func WriteObjectReplyToDB(db *sql.DB, obj ObjectBase) {
}
if update {
- if IsObjectLocal(db, e.Id) {
- WriteObjectUpdatesToDB(db, e)
- } else {
- WriteObjectUpdatesToCache(db, e)
- }
+ WriteObjectUpdatesToDB(db, e)
}
}
@@ -284,6 +296,53 @@ func WriteObjectReplyToDB(db *sql.DB, obj ObjectBase) {
}
}
+func WriteActorObjectReplyToDB(db *sql.DB, obj ObjectBase) {
+ for _, e := range obj.InReplyTo {
+ query := `select id from replies where id=$1 and inreplyto=$2`
+
+ rows, err := db.Query(query, obj.Id, e.Id)
+
+ CheckError(err, "error selecting replies db")
+
+ defer rows.Close()
+
+ var id string
+ rows.Next()
+ rows.Scan(&id)
+
+ if id == "" {
+ query := `insert into replies (id, inreplyto) values ($1, $2)`
+
+ _, err := db.Exec(query, obj.Id, e.Id)
+
+
+ CheckError(err, "error inserting replies db")
+ }
+ }
+
+ if len(obj.InReplyTo) < 1 {
+ query := `select id from replies where id=$1 and inreplyto=$2`
+
+ rows, err := db.Query(query, obj.Id, "")
+
+ CheckError(err, "error selecting replies db")
+
+ defer rows.Close()
+
+ var id string
+ rows.Next()
+ rows.Scan(&id)
+
+ if id == "" {
+ query := `insert into replies (id, inreplyto) values ($1, $2)`
+
+ _, err := db.Exec(query, obj.Id, "")
+
+ CheckError(err, "error inserting replies db")
+ }
+ }
+}
+
func WriteWalletToDB(db *sql.DB, obj ObjectBase) {
for _, e := range obj.Option {
if e == "wallet" {
@@ -303,11 +362,11 @@ func WriteActivitytoDB(db *sql.DB, obj ObjectBase) {
obj.Name = EscapeString(obj.Name)
obj.Content = EscapeString(obj.Content)
- obj.AttributedTo = EscapeString(obj.AttributedTo)
+ obj.AttributedTo = EscapeString(obj.AttributedTo)
- query := `insert into activitystream (id, type, name, content, published, updated, attributedto, actor) values ($1, $2, $3, $4, $5, $6, $7, $8)`
+ 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.Id)
+ _, 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")
@@ -321,9 +380,9 @@ func WriteActivitytoDBWithAttachment(db *sql.DB, obj ObjectBase, attachment Obje
obj.Content = EscapeString(obj.Content)
obj.AttributedTo = EscapeString(obj.AttributedTo)
- query := `insert into activitystream (id, type, name, content, attachment, preview, published, updated, attributedto, actor) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`
+ 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.Id)
+ _, 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")
@@ -360,7 +419,7 @@ func GetActivityFromDB(db *sql.DB, id string) Collection {
nColl.Actor = &nActor
- query := `select actor, id, name, content, type, published, updated, attributedto, attachment, preview, actor from activitystream where id=$1 order by updated asc`
+ query := `select actor, id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from activitystream where id=$1 order by updated asc`
rows, err := db.Query(query, id)
@@ -373,11 +432,11 @@ func GetActivityFromDB(db *sql.DB, id string) Collection {
var attachID 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)
+ 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)
CheckError(err, "error scan object into post struct")
- post.Actor = &actor
+ post.Actor = actor.Id
var postCnt int
var imgCnt int
@@ -402,7 +461,7 @@ func GetObjectFromDBPage(db *sql.DB, id string, page int) Collection {
var nColl Collection
var result []ObjectBase
- 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 from (select id, name, content, type, published, updated, attributedto, attachment, preview, actor 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 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 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 8 offset $2`
+ 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 8 offset $2`
rows, err := db.Query(query, id, page * 8)
@@ -416,11 +475,11 @@ func GetObjectFromDBPage(db *sql.DB, id string, page int) Collection {
var attachID string
var previewID string
- err = rows.Scan(&count, &post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.Updated, &post.AttributedTo, &attachID, &previewID, &actor.Id)
+ err = rows.Scan(&count, &post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.Updated, &post.AttributedTo, &attachID, &previewID, &actor.Id, &post.TripCode, &post.Sensitive)
CheckError(err, "error scan object into post struct")
- post.Actor = &actor
+ post.Actor = actor.Id
var postCnt int
var imgCnt int
@@ -446,7 +505,7 @@ func GetObjectFromDB(db *sql.DB, id string) Collection {
var nColl Collection
var result []ObjectBase
- query := `select id, name, content, type, published, updated, attributedto, attachment, preview, actor from activitystream where actor=$1 and id in (select id from replies where inreplyto='') and type='Note' order by updated asc`
+ query := `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' order by updated desc`
rows, err := db.Query(query, id)
@@ -459,11 +518,11 @@ func GetObjectFromDB(db *sql.DB, id string) Collection {
var attachID string
var previewID string
- err = rows.Scan(&post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.Updated, &post.AttributedTo, &attachID, &previewID, &actor.Id)
+ err = rows.Scan(&post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.Updated, &post.AttributedTo, &attachID, &previewID, &actor.Id, &post.TripCode, &post.Sensitive)
CheckError(err, "error scan object into post struct")
- post.Actor = &actor
+ post.Actor = actor.Id
var postCnt int
var imgCnt int
@@ -488,7 +547,7 @@ func GetObjectFromDBCatalog(db *sql.DB, id string) Collection {
var nColl Collection
var result []ObjectBase
- query := `select x.id, x.name, x.content, x.type, x.published, x.updated, x.attributedto, x.attachment, x.preview, x.actor from (select id, name, content, type, published, updated, attributedto, attachment, preview, actor 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 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 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`
+ query := `select 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`
rows, err := db.Query(query, id)
@@ -501,11 +560,11 @@ func GetObjectFromDBCatalog(db *sql.DB, id string) Collection {
var attachID string
var previewID string
- err = rows.Scan(&post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.Updated, &post.AttributedTo, &attachID, &previewID, &actor.Id)
+ err = rows.Scan(&post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.Updated, &post.AttributedTo, &attachID, &previewID, &actor.Id, &post.TripCode, &post.Sensitive)
CheckError(err, "error scan object into post struct")
- post.Actor = &actor
+ post.Actor = actor.Id
var replies CollectionBase
@@ -529,7 +588,7 @@ func GetObjectByIDFromDB(db *sql.DB, postID string) Collection {
var nColl Collection
var result []ObjectBase
- query := `select x.id, x.name, x.content, x.type, x.published, x.updated, x.attributedto, x.attachment, x.preview, x.actor from (select id, name, content, type, published, updated, attributedto, attachment, preview, actor from activitystream where id=$1 and type='Note' union select id, name, content, type, published, updated, attributedto, attachment, preview, actor from cacheactivitystream where id=$1 and type='Note') as x`
+ query := `select 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 id=$1 and type='Note' union select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from cacheactivitystream where id=$1 and type='Note') as x`
rows, err := db.Query(query, postID)
@@ -542,13 +601,13 @@ func GetObjectByIDFromDB(db *sql.DB, postID string) Collection {
var attachID string
var previewID string
- err = rows.Scan(&post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.Updated, &post.AttributedTo, &attachID, &previewID, &actor.Id)
+ err = rows.Scan(&post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.Updated, &post.AttributedTo, &attachID, &previewID, &actor.Id, &post.TripCode, &post.Sensitive)
CheckError(err, "error scan object into post struct")
actor = GetActorFromDB(db, actor.Id)
- post.Actor = &actor
+ post.Actor = actor.Id
nColl.Actor = &actor
@@ -597,7 +656,7 @@ func GetObjectRepliesDBLimit(db *sql.DB, parent ObjectBase, limit int) (*Collect
var nColl CollectionBase
var result []ObjectBase
- query := `select count(x.id) over(), sum(case when RTRIM(x.attachment) = '' then 0 else 1 end) over(), x.id, x.name, x.content, x.type, x.published, x.attributedto, x.attachment, x.preview, x.actor from (select * from activitystream where id in (select id from replies where inreplyto=$1) and type='Note' union select * from cacheactivitystream where id in (select id from replies where inreplyto=$1) and type='Note') as x order by x.published desc limit $2`
+ query := `select count(x.id) over(), sum(case when RTRIM(x.attachment) = '' then 0 else 1 end) over(), x.id, x.name, x.content, x.type, x.published, x.attributedto, x.attachment, x.preview, x.actor, x.tripcode, x.sensitive from (select * from activitystream where id in (select id from replies where inreplyto=$1) and type='Note' union select * from cacheactivitystream where id in (select id from replies where inreplyto=$1) and type='Note') as x order by x.published desc limit $2`
rows, err := db.Query(query, parent.Id, limit)
@@ -615,11 +674,11 @@ func GetObjectRepliesDBLimit(db *sql.DB, parent ObjectBase, limit int) (*Collect
post.InReplyTo = append(post.InReplyTo, parent)
- err = rows.Scan(&postCount, &attachCount, &post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.AttributedTo, &attachID, &previewID, &actor.Id)
+ err = rows.Scan(&postCount, &attachCount, &post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.AttributedTo, &attachID, &previewID, &actor.Id, &post.TripCode, &post.Sensitive)
CheckError(err, "error with replies db scan")
- post.Actor = &actor
+ post.Actor = actor.Id
var postCnt int
var imgCnt int
@@ -647,7 +706,7 @@ func GetObjectRepliesDB(db *sql.DB, parent ObjectBase) (*CollectionBase, int, in
var nColl CollectionBase
var result []ObjectBase
- query := `select count(x.id) over(), sum(case when RTRIM(x.attachment) = '' then 0 else 1 end) over(), x.id, x.name, x.content, x.type, x.published, x.attributedto, x.attachment, x.preview, x.actor from (select * from activitystream where id in (select id from replies where inreplyto=$1) and type='Note' union select * from cacheactivitystream where id in (select id from replies where inreplyto=$1) and type='Note') as x order by x.published asc`
+ query := `select count(x.id) over(), sum(case when RTRIM(x.attachment) = '' then 0 else 1 end) over(), x.id, x.name, x.content, x.type, x.published, x.attributedto, x.attachment, x.preview, x.actor, x.tripcode, x.sensitive from (select * from activitystream where id in (select id from replies where inreplyto=$1) and type='Note' union select * from cacheactivitystream where id in (select id from replies where inreplyto=$1) and type='Note') as x order by x.published asc`
rows, err := db.Query(query, parent.Id)
@@ -665,11 +724,11 @@ func GetObjectRepliesDB(db *sql.DB, parent ObjectBase) (*CollectionBase, int, in
post.InReplyTo = append(post.InReplyTo, parent)
- err = rows.Scan(&postCount, &attachCount, &post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.AttributedTo, &attachID, &previewID, &actor.Id)
+ err = rows.Scan(&postCount, &attachCount, &post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.AttributedTo, &attachID, &previewID, &actor.Id, &post.TripCode, &post.Sensitive)
CheckError(err, "error with replies db scan")
- post.Actor = &actor
+ post.Actor = actor.Id
var postCnt int
var imgCnt int
@@ -695,7 +754,7 @@ func GetObjectRepliesReplies(db *sql.DB, parent ObjectBase) (*CollectionBase, in
var nColl CollectionBase
var result []ObjectBase
- query := `select id, name, content, type, published, attributedto, attachment, preview, actor from activitystream where id in (select id from replies where inreplyto=$1) and type='Note' order by updated asc`
+ query := `select id, name, content, type, published, attributedto, attachment, preview, actor, tripcode, sensitive from activitystream where id in (select id from replies where inreplyto=$1) and type='Note' order by updated asc`
rows, err := db.Query(query, parent.Id)
@@ -710,11 +769,11 @@ func GetObjectRepliesReplies(db *sql.DB, parent ObjectBase) (*CollectionBase, in
post.InReplyTo = append(post.InReplyTo, parent)
- err = rows.Scan(&post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.AttributedTo, &attachID, &previewID, &actor.Id)
+ err = rows.Scan(&post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.AttributedTo, &attachID, &previewID, &actor.Id, &post.TripCode, &post.Sensitive)
CheckError(err, "error with replies replies db scan")
- post.Actor = &actor
+ post.Actor = actor.Id
post.Attachment = GetObjectAttachment(db, attachID)
@@ -733,7 +792,7 @@ func GetObjectRepliesRepliesDB(db *sql.DB, parent ObjectBase) (*CollectionBase,
var nColl CollectionBase
var result []ObjectBase
- query := `select count(x.id) over(), sum(case when RTRIM(x.attachment) = '' then 0 else 1 end) over(), x.id, x.name, x.content, x.type, x.published, x.attributedto, x.attachment, x.preview, x.actor from (select * from activitystream where id in (select id from replies where inreplyto=$1) and type='Note' union select * from cacheactivitystream where id in (select id from replies where inreplyto=$1) and type='Note') as x order by x.published asc`
+ query := `select count(x.id) over(), sum(case when RTRIM(x.attachment) = '' then 0 else 1 end) over(), x.id, x.name, x.content, x.type, x.published, x.attributedto, x.attachment, x.preview, x.actor, x.tripcode, x.sensitive from (select * from activitystream where id in (select id from replies where inreplyto=$1) and type='Note' union select * from cacheactivitystream where id in (select id from replies where inreplyto=$1) and type='Note') as x order by x.published asc`
rows, err := db.Query(query, parent.Id)
@@ -750,11 +809,11 @@ func GetObjectRepliesRepliesDB(db *sql.DB, parent ObjectBase) (*CollectionBase,
post.InReplyTo = append(post.InReplyTo, parent)
- err = rows.Scan(&postCount, &attachCount, &post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.AttributedTo, &attachID, &previewID, &actor.Id)
+ err = rows.Scan(&postCount, &attachCount, &post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.AttributedTo, &attachID, &previewID, &actor.Id, &post.TripCode, &post.Sensitive)
CheckError(err, "error with replies replies db scan")
- post.Actor = &actor
+ post.Actor = actor.Id
post.Attachment = GetObjectAttachment(db, attachID)
@@ -889,7 +948,7 @@ func GetObjectImgsTotalDB(db *sql.DB, actor Actor) int{
func DeletePreviewFromFile(db *sql.DB, id string) {
- var query = `select href, type from activitystream where id in (select preview from activitystream where id=$1)`
+ var query = `select href from activitystream where id in (select preview from activitystream where id=$1)`
rows, err := db.Query(query, id)
@@ -898,18 +957,43 @@ func DeletePreviewFromFile(db *sql.DB, id string) {
defer rows.Close()
for rows.Next() {
var href string
- var _type string
- err := rows.Scan(&href, &_type)
+
+ err := rows.Scan(&href)
href = strings.Replace(href, Domain + "/", "", 1)
CheckError(err, "error scanning delete attachment")
- if _type != "Tombstone" {
+ if(href != "static/notfound.png") {
_, err = os.Stat(href)
if err == nil {
os.Remove(href)
- }
+ }
}
+ }
+
+}
+
+func RemovePreviewFromFile(db *sql.DB, id string) {
+
+ var query = `select href from activitystream where id in (select preview from activitystream where id=$1)`
+
+ rows, err := db.Query(query, id)
+
+ CheckError(err, "error query delete attachment")
+
+ defer rows.Close()
+ for rows.Next() {
+ var href string
+ err := rows.Scan(&href)
+ href = strings.Replace(href, Domain + "/", "", 1)
+ CheckError(err, "error scanning delete attachment")
+
+ if(href != "static/notfound.png") {
+ _, err = os.Stat(href)
+ if err == nil {
+ os.Remove(href)
+ }
+ }
}
DeletePreviewFromDB(db, id)
@@ -917,7 +1001,7 @@ func DeletePreviewFromFile(db *sql.DB, id string) {
func DeleteAttachmentFromFile(db *sql.DB, id string) {
- var query = `select href, type from activitystream where id in (select attachment from activitystream where id=$1)`
+ var query = `select href from activitystream where id in (select attachment from activitystream where id=$1)`
rows, err := db.Query(query, id)
@@ -926,30 +1010,27 @@ func DeleteAttachmentFromFile(db *sql.DB, id string) {
defer rows.Close()
for rows.Next() {
var href string
- var _type string
- err := rows.Scan(&href, &_type)
+ err := rows.Scan(&href)
href = strings.Replace(href, Domain + "/", "", 1)
-
CheckError(err, "error scanning delete preview")
- if _type != "Tombstone" {
+ if(href != "static/notfound.png") {
_, err = os.Stat(href)
if err == nil {
os.Remove(href)
- }
+ }
}
}
- DeleteAttachmentFromDB(db, id)
}
-func DeletePreviewRepliesFromDB(db *sql.DB, id string) {
+func TombstonePreviewRepliesFromDB(db *sql.DB, id string) {
var query = `select id from activitystream where id in (select id from replies where inreplyto=$1)`
rows, err := db.Query(query, id)
- CheckError(err, "error query delete preview replies")
+ CheckError(err, "error query tombstone preview replies")
defer rows.Close()
for rows.Next() {
@@ -957,18 +1038,19 @@ func DeletePreviewRepliesFromDB(db *sql.DB, id string) {
err := rows.Scan(&attachment)
- CheckError(err, "error scanning delete preview")
+ CheckError(err, "error scanning tombstone preview")
DeletePreviewFromFile(db, attachment)
+ TombstonePreviewFromDB(db, attachment)
}
}
-func DeleteAttachmentRepliesFromDB(db *sql.DB, id string) {
+func TombstoneAttachmentRepliesFromDB(db *sql.DB, id string) {
var query = `select id from activitystream where id in (select id from replies where inreplyto=$1)`
rows, err := db.Query(query, id)
- CheckError(err, "error query delete attachment replies")
+ CheckError(err, "error query tombstone attachment replies")
defer rows.Close()
for rows.Next() {
@@ -979,27 +1061,68 @@ func DeleteAttachmentRepliesFromDB(db *sql.DB, id string) {
CheckError(err, "error scanning delete attachment")
DeleteAttachmentFromFile(db, attachment)
+ TombstoneAttachmentFromDB(db, attachment)
}
}
+func TombstoneAttachmentFromDB(db *sql.DB, id string) {
+ datetime := time.Now().Format(time.RFC3339)
+
+ 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)
+
+ 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)
+
+ CheckError(err, "error with tombstone cache attachment")
+}
+
func DeleteAttachmentFromDB(db *sql.DB, id string) {
+ var query = `delete from activitystream where id in (select attachment from activitystream where id=$1)`
+
+ _, err := db.Exec(query, id)
+
+ CheckError(err, "error with delete attachment")
+
+ query = `delete from cacheactivitystream where id in (select attachment from cacheactivitystream where id=$1)`
+
+ _, err = db.Exec(query, id)
+
+ CheckError(err, "error with delete cache attachment")
+}
+
+func TombstonePreviewFromDB(db *sql.DB, id string) {
datetime := time.Now().Format(time.RFC3339)
- var query = `update activitystream set type='Tombstone', mediatype='image/png', href=$1, name='', content='', attributedto='deleted', updated=$2, deleted=$3 where id in (select attachment from activitystream where id=$4)`
+ 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 + "/public/removed.png", datetime, datetime, id)
+ CheckError(err, "error with tombstone preview")
- CheckError(err, "error with delete attachment")
+ 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)
+
+ CheckError(err, "error with tombstone cache preview")
}
func DeletePreviewFromDB(db *sql.DB, id string) {
- datetime := time.Now().Format(time.RFC3339)
+ var query = `delete from activitystream where id=$1`
- var query = `update activitystream set type='Tombstone', mediatype='image/png', href=$1, name='', content='', attributedto='deleted', updated=$2, deleted=$3 where id in (select preview from activitystream where id=$4)`
+ _, err := db.Exec(query, id)
- _, err := db.Exec(query, Domain + "/public/removed.png", datetime, datetime, id)
+ CheckError(err, "error with delete preview")
- CheckError(err, "error with delete preview")
+ query = `delete from cacheactivitystream where id in (select preview from cacheactivitystream where id=$1)`
+
+ _, err = db.Exec(query, id)
+
+ CheckError(err, "error with delete cache preview")
}
func DeleteObjectRepliedTo(db *sql.DB, id string){
@@ -1009,13 +1132,33 @@ func DeleteObjectRepliedTo(db *sql.DB, id string){
CheckError(err, "error with delete object replies")
}
-func DeleteObjectFromDB(db *sql.DB, id string) {
+func TombstoneObjectFromDB(db *sql.DB, id string) {
datetime := time.Now().Format(time.RFC3339)
- var query = `update activitystream set type='Tombstone', name='', content='', attributedto='deleted', updated=$1, deleted=$2 where id=$3`
+ var query = `update activitystream set type='Tombstone', name='', content='', attributedto='deleted', tripcode='', deleted=$1 where id=$2`
+
+ _, err := db.Exec(query, datetime, id)
+
+ CheckError(err, "error with tombstone object")
+
+ query = `update cacheactivitystream set type='Tombstone', name='', content='', attributedto='deleted', tripcode='', deleted=$1 where id=$2`
- _, err := db.Exec(query, datetime, datetime, id)
+ _, err = db.Exec(query, datetime, id)
+
+ CheckError(err, "error with tombstone cache object")
+}
+
+func DeleteObjectFromDB(db *sql.DB, id string) {
+ var query = `delete from activitystream where id=$1`
+
+ _, err := db.Exec(query, id)
CheckError(err, "error with delete object")
+
+ query = `delete from cacheactivitystream where id=$1`
+
+ _, err = db.Exec(query, id)
+
+ CheckError(err, "error with delete cache object")
}
func DeleteObjectsInReplyTo(db *sql.DB, id string) {
@@ -1026,44 +1169,157 @@ func DeleteObjectsInReplyTo(db *sql.DB, id string) {
CheckError(err, "error with delete object replies to")
}
-func DeleteObjectRepliesFromDB(db *sql.DB, id string) {
+func TombstoneObjectRepliesFromDB(db *sql.DB, id string) {
datetime := time.Now().Format(time.RFC3339)
- var query = `update activitystream set type='Tombstone', name='', content='', attributedto='deleted', updated=$1, deleted=$2 where id in (select id from replies where inreplyto=$3)`
+ var query = `update activitystream set type='Tombstone', name='', content='', attributedto='deleted', tripcode='', deleted=$1 where id in (select id from replies where inreplyto=$2)`
+
+ _, err := db.Exec(query, datetime, id)
+ CheckError(err, "error with tombstone object replies")
+
+ query = `update cacheactivitystream set type='Tombstone', name='', content='', attributedto='deleted', tripcode='', deleted=$1 where id in (select id from replies where inreplyto=$2)`
- _, err := db.Exec(query, datetime, datetime, id)
- CheckError(err, "error with delete object replies")
+ _, err = db.Exec(query, datetime, id)
+ CheckError(err, "error with tombstone object cache replies")
}
-func DeleteObject(db *sql.DB, id string) {
+func SetAttachmentFromDB(db *sql.DB, id string, _type string) {
+ datetime := time.Now().Format(time.RFC3339)
+
+ var query = `update activitystream set type=$1, deleted=$2 where id in (select attachment from activitystream where id=$3)`
+
+ _, err := db.Exec(query, _type, datetime, id)
+
+ CheckError(err, "error with set attachment")
+
+ query = `update cacheactivitystream set type=$1, deleted=$2 where id in (select attachment from cacheactivitystream where id=$3)`
+
+ _, err = db.Exec(query, _type, datetime, id)
+
+ CheckError(err, "error with set cache attachment")
+}
+
+func SetAttachmentRepliesFromDB(db *sql.DB, id string, _type string) {
+ datetime := time.Now().Format(time.RFC3339)
+
+ var query = `update activitystream set type=$1, deleted=$2 where id in (select attachment from activitystream where id in (select id from replies where inreplyto=$3))`
+
+ _, err := db.Exec(query, _type, datetime, id)
+
+ CheckError(err, "error with set attachment")
+
+ query = `update cacheactivitystream set type=$1, deleted=$2 where id in (select attachment from cacheactivitystream where id in (select id from replies where inreplyto=$3))`
+
+ _, err = db.Exec(query, _type, datetime, id)
+
+ CheckError(err, "error with set cache attachment")
+}
+
+func SetPreviewFromDB(db *sql.DB, id string, _type string) {
+ datetime := time.Now().Format(time.RFC3339)
+
+ var query = `update activitystream set type=$1, deleted=$2 where id in (select preview from activitystream where id=$3)`
+
+ _, err := db.Exec(query, _type, datetime, id)
+
+ CheckError(err, "error with set preview")
+
+ query = `update cacheactivitystream set type=$1, deleted=$2 where id in (select preview from cacheactivitystream where id=$3)`
+
+ _, err = db.Exec(query, _type, datetime, id)
+
+ CheckError(err, "error with set cache preview")
+}
+
+func SetPreviewRepliesFromDB(db *sql.DB, id string, _type string) {
+ datetime := time.Now().Format(time.RFC3339)
+
+ var query = `update activitystream set type=$1, deleted=$2 where id in (select preview from activitystream where id in (select id from replies where inreplyto=$3))`
+
+ _, err := db.Exec(query, _type, datetime, id)
+
+ CheckError(err, "error with set preview")
+
+ query = `update cacheactivitystream set type=$1, deleted=$2 where id in (select preview from cacheactivitystream where id in (select id from replies where inreplyto=$3))`
+
+ _, err = db.Exec(query, _type, datetime, id)
+
+ CheckError(err, "error with set cache preview")
+}
+
+func SetObjectFromDB(db *sql.DB, id string, _type string) {
+ datetime := time.Now().Format(time.RFC3339)
- if(!IsIDLocal(db, id)) {
- return
- }
+ var query = `update activitystream set type=$1, deleted=$2 where id=$3`
- DeleteReportActivity(db, id)
+ _, err := db.Exec(query, _type, datetime, id)
+
+ CheckError(err, "error with set object")
+
+ query = `update cacheactivitystream set type=$1, deleted=$2 where id=$3`
+
+ _, err = db.Exec(query, _type, datetime, id)
+
+ CheckError(err, "error with set cache object")
+}
+
+func SetObjectRepliesFromDB(db *sql.DB, id string, _type string) {
+ datetime := time.Now().Format(time.RFC3339)
+
+ var query = `update activitystream set type=$1, deleted=$2 where id in (select id from replies where inreplyto=$3)`
+ _, err := db.Exec(query, _type, datetime, id)
+ CheckError(err, "error with set object replies")
+
+ query = `update cacheactivitystream set type=$1, deleted=$2 where id in (select id from replies where inreplyto=$3)`
+ _, err = db.Exec(query, _type, datetime, id)
+ CheckError(err, "error with set cache object replies")
+}
+
+func SetObject(db *sql.DB, id string, _type string) {
+ 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);
+}
+
+func DeleteObject(db *sql.DB, id string) {
+ DeleteReportActivity(db, id)
DeleteAttachmentFromFile(db, id)
- DeletePreviewFromFile(db, id)
+ DeleteAttachmentFromDB(db, id)
+ DeletePreviewFromFile(db, id)
+ DeletePreviewFromDB(db, id)
DeleteObjectFromDB(db, id)
DeleteObjectRepliedTo(db, id)
}
-func DeleteObjectAndReplies(db *sql.DB, id string) {
-
- if(!IsIDLocal(db, id)) {
- return
- }
+func TombstoneObject(db *sql.DB, id string) {
+ DeleteReportActivity(db, id)
+ DeleteAttachmentFromFile(db, id)
+ TombstoneAttachmentFromDB(db, id)
+ DeletePreviewFromFile(db, id)
+ TombstonePreviewFromDB(db, id)
+ TombstoneObjectFromDB(db, id)
+}
+func TombstoneObjectAndReplies(db *sql.DB, id string) {
DeleteReportActivity(db, id)
DeleteAttachmentFromFile(db, id)
+ TombstoneAttachmentFromDB(db, id)
DeletePreviewFromFile(db, id)
- DeleteObjectRepliedTo(db, id)
- DeleteObjectsInReplyTo(db, id)
- DeleteObjectRepliesFromDB(db, id)
- DeleteAttachmentRepliesFromDB(db, id)
- DeletePreviewRepliesFromDB(db, id)
- DeleteObjectFromDB(db, id)
+ TombstonePreviewFromDB(db, id)
+ TombstoneObjectRepliesFromDB(db, id)
+ TombstoneAttachmentRepliesFromDB(db, id)
+ TombstonePreviewRepliesFromDB(db, id)
+ TombstoneObjectFromDB(db, id)
}
func GetRandomCaptcha(db *sql.DB) string{
@@ -1160,11 +1416,7 @@ func DeleteCaptchaCodeDB(db *sql.DB, verify string) {
}
func EscapeString(text string) string {
- // re := regexp.MustCompile("(?i)(n)+(\\s+)?(i)+(\\s+)?(g)+(\\s+)?(e)+?(\\s+)?(r)+(\\s+)?")
- // text = re.ReplaceAllString(text, "I love black people")
- // re = regexp.MustCompile("(?i)(n)+(\\s+)?(i)+(\\s+)?(g)(\\s+)?(g)+(\\s+)?")
- // text = re.ReplaceAllString(text, "I love black people")
- // text = strings.Replace(text, "<", "&lt;", -1)
+ text = strings.Replace(text, "<", "&lt;", -1)
return text
}
@@ -1206,3 +1458,33 @@ func GetActorReportedDB(db *sql.DB, id string) []ObjectBase {
return nObj
}
+
+func GetActorPemFromDB(db *sql.DB, pemID string) PublicKeyPem {
+ query := `select id, owner, file from publickeypem where id=$1`
+ rows, err := db.Query(query, pemID)
+
+ CheckError(err, "could not get public key pem from database")
+
+ var pem PublicKeyPem
+
+ defer rows.Close()
+ rows.Next()
+ rows.Scan(&pem.Id, &pem.Owner, &pem.PublicKeyPem)
+ f, _ := os.ReadFile(pem.PublicKeyPem)
+
+ pem.PublicKeyPem = strings.ReplaceAll(string(f), "\r\n", `\n`)
+
+ return pem
+}
+
+func MarkObjectSensitive(db *sql.DB, id string, sensitive bool) {
+ var query = `update activitystream set sensitive=$1 where id=$2`
+ _, err := db.Exec(query, sensitive, id)
+
+ CheckError(err, "error updating sensitive object in activitystream")
+
+ query = `update cacheactivitystream set sensitive=$1 where id=$2`
+ _, err = db.Exec(query, sensitive, id)
+
+ CheckError(err, "error updating sensitive object in cacheactivitystream")
+}
diff --git a/databaseschema.psql b/databaseschema.psql
index d168882..e12813e 100644
--- a/databaseschema.psql
+++ b/databaseschema.psql
@@ -205,4 +205,18 @@ CONSTRAINT fk_object FOREIGN KEY (object) REFERENCES cacheactivitystream(id)
CREATE TABLE IF NOT EXISTS removed(
id varchar(100),
type varchar(25)
-); \ No newline at end of file
+);
+
+ALTER TABLE activitystream ADD COLUMN IF NOT EXISTS tripcode varchar(50) default '';
+ALTER TABLE cacheactivitystream ADD COLUMN IF NOT EXISTS tripcode varchar(50) default '';
+
+CREATE TABLE IF NOT EXISTS publicKeyPem(
+id varchar(100) UNIQUE,
+owner varchar(100),
+file varchar(100)
+);
+
+ALTER TABLE actor ADD COLUMN IF NOT EXISTS publicKeyPem varchar(100) default '';
+
+ALTER TABLE activitystream ADD COLUMN IF NOT EXISTS sensitive boolean default false;
+ALTER TABLE cacheactivitystream ADD COLUMN IF NOT EXISTS sensitive boolean default false; \ No newline at end of file
diff --git a/Follow.go b/follow.go
index 667c6bd..7c56136 100644
--- a/Follow.go
+++ b/follow.go
@@ -118,19 +118,17 @@ func AcceptFollow(activity Activity) Activity {
var accept Activity
accept.AtContext.Context = activity.AtContext.Context
accept.Type = "Accept"
- accept.Actor = activity.Object.Actor
+ var nActor Actor
+ accept.Actor = &nActor
+ accept.Actor.Id = activity.Object.Actor
var nObj ObjectBase
- var nActor Actor
accept.Object = &nObj
- accept.Object.Actor = &nActor
- accept.Object.Actor = activity.Actor
+ accept.Object.Actor = activity.Actor.Id
var nNested NestedObjectBase
- var mActor Actor
accept.Object.Object = &nNested
- accept.Object.Object.Actor = &mActor
accept.Object.Object.Actor = activity.Object.Actor
accept.Object.Object.Type = "Follow"
- accept.To = append(accept.To, activity.Object.Actor.Id)
+ accept.To = append(accept.To, activity.Object.Actor)
return accept
}
@@ -140,15 +138,13 @@ func RejectActivity(activity Activity) Activity {
accept.AtContext.Context = activity.AtContext.Context
accept.Type = "Reject"
var nObj ObjectBase
- var nActor Actor
accept.Object = &nObj
- accept.Object.Actor = &nActor
- accept.Actor = activity.Object.Actor
- accept.Object.Actor = activity.Actor
+ var nActor Actor
+ accept.Actor = &nActor
+ accept.Actor.Id = activity.Object.Actor
+ accept.Object.Actor = activity.Actor.Id
var nNested NestedObjectBase
- var mActor Actor
accept.Object.Object = &nNested
- accept.Object.Object.Actor = &mActor
accept.Object.Object.Actor = activity.Object.Actor
accept.Object.Object.Type = "Follow"
accept.To = append(accept.To, activity.Actor.Id)
@@ -162,19 +158,20 @@ func SetActorFollowerDB(db *sql.DB, activity Activity) Activity {
followers := GetActorFollowDB(db, activity.Actor.Id)
for _, e := range followers {
- if e.Id == activity.Object.Actor.Id {
+ if e.Id == activity.Object.Actor {
alreadyFollow = true
}
}
+
if alreadyFollow {
query = `delete from follower where id=$1 and follower=$2`
- activity.Summary = activity.Object.Actor.Id + " Unfollow " + activity.Actor.Id
+ activity.Summary = activity.Object.Actor + " Unfollow " + activity.Actor.Id
} else {
query = `insert into follower (id, follower) values ($1, $2)`
- activity.Summary = activity.Object.Actor.Id + " Follow " + activity.Actor.Id
+ activity.Summary = activity.Object.Actor + " Follow " + activity.Actor.Id
}
- _, err := db.Exec(query, activity.Actor.Id, activity.Object.Actor.Id)
+ _, err := db.Exec(query, activity.Actor.Id, activity.Object.Actor)
if CheckError(err, "error with follower db insert/delete") != nil {
activity.Type = "Reject"
@@ -188,7 +185,7 @@ func SetActorFollowerDB(db *sql.DB, activity Activity) Activity {
func SetActorFollowingDB(db *sql.DB, activity Activity) Activity {
var query string
alreadyFollow := false
- following := GetActorFollowingDB(db, activity.Object.Actor.Id)
+ following := GetActorFollowingDB(db, activity.Object.Actor)
for _, e := range following {
if e.Id == activity.Actor.Id {
@@ -198,19 +195,19 @@ func SetActorFollowingDB(db *sql.DB, activity Activity) Activity {
if alreadyFollow {
query = `delete from following where id=$1 and following=$2`
- activity.Summary = activity.Object.Actor.Id + " Unfollowing " + activity.Actor.Id
+ activity.Summary = activity.Object.Actor + " Unfollowing " + activity.Actor.Id
if !IsActorLocal(db, activity.Actor.Id) {
go DeleteActorCache(db, activity.Actor.Id)
}
} else {
query = `insert into following (id, following) values ($1, $2)`
- activity.Summary = activity.Object.Actor.Id + " Following " + activity.Actor.Id
+ activity.Summary = activity.Object.Actor + " Following " + activity.Actor.Id
if !IsActorLocal(db, activity.Actor.Id) {
go WriteActorToCache(db, activity.Actor.Id)
}
}
- _, err := db.Exec(query, activity.Object.Actor.Id, activity.Actor.Id)
+ _, err := db.Exec(query, activity.Object.Actor, activity.Actor.Id)
if CheckError(err, "error with following db insert/delete") != nil {
activity.Type = "Reject"
diff --git a/main.go b/main.go
index cedc09c..4333d36 100644
--- a/main.go
+++ b/main.go
@@ -23,11 +23,12 @@ import "github.com/gofrs/uuid"
var Port = ":" + GetConfigValue("instanceport")
var TP = GetConfigValue("instancetp")
-var Domain = TP + "" + GetConfigValue("instance")
+var Instance = GetConfigValue("instance")
+var Domain = TP + "" + Instance
var authReq = []string{"captcha","email","passphrase"}
-var supportedFiles = []string{"image/gif","image/jpeg","image/png","image/svg+xml","image/svg","image/webp","image/avif","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")
@@ -37,13 +38,12 @@ var SiteEmailPort = GetConfigValue("emailport") //587
var TorProxy = "127.0.0.1:9050"
var ldjson = "application/ld+json"
+
var activitystreams = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
func main() {
- if _, err := os.Stat("./public"); os.IsNotExist(err) {
- os.Mkdir("./public", 0755)
- }
+ CreatedNeededDirectories()
InitCache()
@@ -51,6 +51,8 @@ func main() {
defer db.Close()
+ RunDatabaseSchema(db)
+
go MakeCaptchas(db, 100)
*Key = CreateClientKey()
@@ -138,7 +140,7 @@ func main() {
}
if mainActor {
- if accept == activitystreams || accept == ldjson {
+ if acceptActivity(accept) {
GetActorInfo(w, db, Domain)
return
}
@@ -181,7 +183,7 @@ func main() {
}
if actorMain || actorMainPage {
- if accept == activitystreams || accept == ldjson {
+ if acceptActivity(accept) {
GetActorInfo(w, db, actor.Id)
return
}
@@ -190,6 +192,7 @@ func main() {
page, _ := strconv.Atoi(postNum)
collection, valid := WantToServePage(db, actor.Name, page)
+
if valid {
OutboxGet(w, r, db, collection)
}
@@ -268,7 +271,7 @@ func main() {
//catch all
if actorPost {
- if accept == activitystreams || accept == ldjson {
+ if acceptActivity(accept) {
GetActorPost(w, db, path)
return
}
@@ -292,6 +295,12 @@ func main() {
return
}
+ 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") == ""){
w.Write([]byte("Comment or Subject required"))
@@ -334,7 +343,13 @@ func main() {
for key, r0 := range r.Form {
if(key == "captcha") {
err := we.WriteField(key, r.FormValue("captchaCode") + ":" + r.FormValue("captcha"))
- CheckError(err, "error with writing field")
+ CheckError(err, "error with writing captcha field")
+ }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{
err := we.WriteField(key, r0[0])
CheckError(err, "error with writing field")
@@ -348,12 +363,12 @@ func main() {
we.Close()
- req, err := http.NewRequest("POST", r.FormValue("sendTo"), &b)
+ sendTo := r.FormValue("sendTo")
+ req, err := http.NewRequest("POST", sendTo, &b)
CheckError(err, "error with post form req")
-
+
req.Header.Set("Content-Type", we.FormDataContentType())
- req.Header.Set("Authorization", "Basic " + *Key)
resp, err := http.DefaultClient.Do(req)
@@ -424,46 +439,34 @@ func main() {
followActivity.AtContext.Context = "https://www.w3.org/ns/activitystreams"
followActivity.Type = "Follow"
+
+ var obj ObjectBase
var nactor Actor
- var obj ObjectBase
+ if r.FormValue("actor") == Domain {
+ nactor = GetActorFromDB(db, r.FormValue("actor"))
+ } else {
+ nactor = FingerActor(r.FormValue("actor"))
+ }
+
followActivity.Actor = &nactor
followActivity.Object = &obj
- followActivity.Actor.Id = r.FormValue("actor")
- var mactor Actor
- followActivity.Object.Actor = &mactor
- followActivity.Object.Actor.Id = r.FormValue("follow")
+ followActivity.Object.Actor = r.FormValue("follow")
followActivity.To = append(followActivity.To, r.FormValue("follow"))
- if followActivity.Actor.Id == Domain && !IsActorLocal(db, followActivity.Object.Actor.Id) {
+ if followActivity.Actor.Id == Domain && !IsActorLocal(db, followActivity.Object.Actor) {
w.Write([]byte("main board can only follow local boards. Create a new board and then follow outside boards from it."))
return
}
- enc, _ := json.Marshal(followActivity)
-
- req, err := http.NewRequest("POST", actor.Outbox, bytes.NewBuffer(enc))
-
- CheckError(err, "error with follow req")
-
- _, pass := GetPasswordFromSession(r)
-
- pass = CreateTripCode(pass)
- pass = CreateTripCode(pass)
-
- req.Header.Set("Authorization", "Basic " + pass)
-
- req.Header.Set("Content-Type", activitystreams)
-
- _, err = http.DefaultClient.Do(req)
-
- CheckError(err, "error with add board follow resp")
+ MakeActivityRequestOutbox(db, followActivity)
- FollowingBoards = GetActorFollowingDB(db, Domain)
-
- Boards = GetBoardCollection(db)
+ var redirect string
+ if(actor.Name != "main") {
+ redirect = "/" + actor.Name
+ }
- http.Redirect(w, r, r.Header.Get("Referer"), http.StatusSeeOther)
+ http.Redirect(w, r, "/" + *Key + "/" + redirect, http.StatusSeeOther)
} else if manage && actor.Name != "" {
t := template.Must(template.ParseFiles("./static/main.html", "./static/manage.html"))
@@ -514,7 +517,7 @@ func main() {
adminData.Key = *Key
adminData.Board.TP = TP
- adminData.Board.Post.Actor = &actor
+ adminData.Board.Post.Actor = actor.Id
t.ExecuteTemplate(w, "layout", adminData)
@@ -547,7 +550,7 @@ func main() {
adminData.Boards = Boards
- adminData.Board.Post.Actor = &actor
+ adminData.Board.Post.Actor = actor.Id
t.ExecuteTemplate(w, "layout", adminData)
}
@@ -575,67 +578,18 @@ func main() {
newActorActivity.AtContext.Context = "https://www.w3.org/ns/activitystreams"
newActorActivity.Type = "New"
- var nactor Actor
- var nobj ObjectBase
- newActorActivity.Actor = &nactor
- newActorActivity.Object = &nobj
- newActorActivity.Actor.Id = actor.Id
- newActorActivity.Object.Actor = &board
-
-
- enc, _ := json.Marshal(newActorActivity)
-
- req, err := http.NewRequest("POST", actor.Outbox, bytes.NewBuffer(enc))
-
- CheckError(err, "error with add board follow req")
-
- _, pass := GetPasswordFromSession(r)
-
- pass = CreateTripCode(pass)
- pass = CreateTripCode(pass)
-
- req.Header.Set("Authorization", "Basic " + pass)
- req.Header.Set("Content-Type", activitystreams)
-
- resp, err := http.DefaultClient.Do(req)
-
- CheckError(err, "error with add board follow resp")
- defer resp.Body.Close()
-
- body, _ := ioutil.ReadAll(resp.Body)
-
- var respActor Actor
-
- err = json.Unmarshal(body, &respActor)
-
- CheckError(err, "error getting actor from body in new board")
-
- //update board list with new instances following
- if resp.StatusCode == 200 {
- var board []ObjectBase
- var item ObjectBase
- var removed bool = false
-
- item.Id = respActor.Id
- for _, e := range FollowingBoards {
- if e.Id != item.Id {
- board = append(board, e)
- } else {
- removed = true
- }
- }
-
- if !removed {
- board = append(board, item)
- }
-
- FollowingBoards = board
+ var nobj ObjectBase
+ newActorActivity.Actor = &actor
+ newActorActivity.Object = &nobj
- Boards = GetBoardCollection(db)
- }
+ newActorActivity.Object.Alias = board.Name
+ newActorActivity.Object.Name = board.PreferredUsername
+ newActorActivity.Object.Summary = board.Summary
+ newActorActivity.Object.Sensitive = board.Restricted
- http.Redirect(w, r, r.Header.Get("Referer"), http.StatusSeeOther)
+ MakeActivityRequestOutbox(db, newActorActivity)
+ http.Redirect(w, r, "/" + *Key, http.StatusSeeOther)
})
http.HandleFunc("/verify", func(w http.ResponseWriter, r *http.Request){
@@ -689,15 +643,102 @@ func main() {
http.Redirect(w, r, "/", http.StatusSeeOther)
}
} else {
- t := template.Must(template.ParseFiles("./static/verify.html"))
- t.Execute(w, "")
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte("404 no path"))
}
})
http.HandleFunc("/delete", func(w http.ResponseWriter, r *http.Request){
id := r.URL.Query().Get("id")
+ board := r.URL.Query().Get("board")
+
+ _, auth := GetPasswordFromSession(r)
+
+ if id == "" || auth == "" {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ return
+ }
+
+ manage := r.URL.Query().Get("manage")
+ col := GetCollectionFromID(id)
+
+ if len(col.OrderedItems) < 1 {
+ if !HasAuth(db, auth, GetActorByNameFromDB(db, board).Id) {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ return
+ }
+
+ if !CheckIfObjectOP(db, id) {
+ TombstoneObject(db, id)
+ } else {
+ TombstoneObjectAndReplies(db, id)
+ }
+
+ if(manage == "t"){
+ http.Redirect(w, r, "/" + *Key + "/" + board , http.StatusSeeOther)
+ return
+ } else {
+ http.Redirect(w, r, "/" + board, http.StatusSeeOther)
+ return
+ }
+ }
+
+ actor := col.OrderedItems[0].Actor
+
+ if !HasAuth(db, auth, actor) {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ return
+ }
+
+ var obj ObjectBase
+ obj.Id = id
+ obj.Actor = actor
+
+ isOP := CheckIfObjectOP(db, obj.Id)
+
+ var OP string
+ if len(col.OrderedItems[0].InReplyTo) > 0 {
+ OP = col.OrderedItems[0].InReplyTo[0].Id
+ }
+
+ if !isOP {
+ TombstoneObject(db, id)
+ } else {
+ TombstoneObjectAndReplies(db, id)
+ }
+
+ if IsIDLocal(db, id){
+ DeleteObjectRequest(db, id)
+ }
+
+ 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)
+ return
+ } else {
+ http.Redirect(w, r, OP, http.StatusSeeOther)
+ return
+ }
+ } else {
+ 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){
+
+ id := r.URL.Query().Get("id")
board := r.URL.Query().Get("board")
- actor := GetActorFromPath(db, id, "/")
+
_, auth := GetPasswordFromSession(r)
if id == "" || auth == "" {
@@ -705,36 +746,175 @@ func main() {
w.Write([]byte(""))
return
}
+
+ manage := r.URL.Query().Get("manage")
+ col := GetCollectionFromID(id)
- if !HasAuth(db, auth, actor.Id) {
+ if len(col.OrderedItems) < 1 {
+ if !HasAuth(db, auth, GetActorByNameFromDB(db, board).Id) {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ return
+ }
+
+ DeleteAttachmentFromFile(db, id)
+ TombstoneAttachmentFromDB(db, id)
+
+ DeletePreviewFromFile(db, id)
+ TombstonePreviewFromDB(db, id)
+
+ if(manage == "t"){
+ http.Redirect(w, r, "/" + *Key + "/" + board , http.StatusSeeOther)
+ return
+ } else {
+ 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 != "") {
+ OP = col.OrderedItems[0].InReplyTo[0].Id
+ } else {
+ OP = id
+ }
+
+ if !HasAuth(db, auth, actor) {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(""))
return
}
- if !IsIDLocal(db, id) {
- CreateLocalDeleteDB(db, id, "post")
- CloseLocalReportDB(db, id, board)
- http.Redirect(w, r, r.Header.Get("Referer"), http.StatusSeeOther)
+ DeleteAttachmentFromFile(db, id)
+ TombstoneAttachmentFromDB(db, id)
+
+ DeletePreviewFromFile(db, id)
+ TombstonePreviewFromDB(db, id)
+
+ 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)
+ 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){
+
+ id := r.URL.Query().Get("id")
+ board := r.URL.Query().Get("board")
+
+ _, auth := GetPasswordFromSession(r)
+
+ if id == "" || auth == "" {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ return
+ }
+
+ col := GetCollectionFromID(id)
+
+ if len(col.OrderedItems) < 1 {
+ if !HasAuth(db, auth, GetActorByNameFromDB(db, board).Id) {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ return
+ }
+
+ MarkObjectSensitive(db, id, true)
+
+ 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 != "") {
+ OP = col.OrderedItems[0].InReplyTo[0].Id
+ } else {
+ OP = id
+ }
+
+ if !HasAuth(db, auth, actor) {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ return
+ }
+
+ MarkObjectSensitive(db, id, true)
+
+ if !IsIDLocal(db, OP) {
+ 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){
+ id := r.URL.Query().Get("id")
+ manage := r.URL.Query().Get("manage")
+ board := r.URL.Query().Get("board")
+ col := GetCollectionFromID(id)
+ actor := col.OrderedItems[0].Actor
+ _, auth := GetPasswordFromSession(r)
+
+ if id == "" || auth == "" {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
return
}
+ if !HasAuth(db, auth, actor) {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ return
+ }
var obj ObjectBase
obj.Id = id
- obj.Actor = &actor
+ obj.Actor = actor
- isOP := CheckIfObjectOP(db, obj.Id)
+ isOP := CheckIfObjectOP(db, obj.Id)
+
+ var OP string
+ if len(col.OrderedItems[0].InReplyTo) > 0 {
+ OP = col.OrderedItems[0].InReplyTo[0].Id
+ }
if !isOP {
- DeleteObjectRequest(db, id)
- DeleteObject(db, obj.Id)
- http.Redirect(w, r, r.Header.Get("Referer"), http.StatusSeeOther)
+ SetObject(db, id, "Removed")
+ } else {
+ SetObjectAndReplies(db, id, "Removed")
+ }
+
+ 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)
+ return
+ } else {
+ http.Redirect(w, r, OP, http.StatusSeeOther)
+ return
+ }
} else {
- DeleteObjectAndRepliesRequest(db, id)
- DeleteObjectAndReplies(db, obj.Id)
- http.Redirect(w, r, r.Header.Get("Referer"), http.StatusSeeOther)
+ http.Redirect(w, r, "/" + board, http.StatusSeeOther)
return
}
@@ -742,10 +922,21 @@ func main() {
w.Write([]byte(""))
})
- http.HandleFunc("/deleteattach", 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")
+ board := r.URL.Query().Get("board")
+ col := GetCollectionFromID(id)
+ actor := col.OrderedItems[0].Actor
+ var OP string
+ if (len(col.OrderedItems[0].InReplyTo) > 0 && col.OrderedItems[0].InReplyTo[0].Id != "") {
+ OP = col.OrderedItems[0].InReplyTo[0].Id
+ } else {
+ OP = id
+ }
+
_, auth := GetPasswordFromSession(r)
if id == "" || auth == "" {
@@ -754,24 +945,29 @@ func main() {
return
}
- actor := GetActorFromPath(db, id, "/")
-
- if !HasAuth(db, auth, actor.Id) {
+ if !HasAuth(db, auth, actor) {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(""))
return
- }
+ }
- if !IsIDLocal(db, id) {
- CreateLocalDeleteDB(db, id, "attachment")
- http.Redirect(w, r, r.Header.Get("Referer"), http.StatusSeeOther)
+ SetAttachmentFromDB(db, id, "Removed")
+ SetPreviewFromDB(db, id, "Removed")
+
+ 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)
+ return
+ } else {
+ http.Redirect(w, r, OP, http.StatusSeeOther)
return
}
- DeleteAttachmentFromFile(db, id)
- DeletePreviewFromFile(db, id)
- http.Redirect(w, r, r.Header.Get("Referer"), http.StatusSeeOther)
- })
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ })
http.HandleFunc("/report", func(w http.ResponseWriter, r *http.Request){
@@ -807,13 +1003,13 @@ func main() {
if !IsIDLocal(db, id) {
CloseLocalReportDB(db, id, board)
- http.Redirect(w, r, r.Header.Get("Referer"), http.StatusSeeOther)
+ http.Redirect(w, r, "/" + *Key + "/" + board, http.StatusSeeOther)
return
}
reported := DeleteReportActivity(db, id)
if reported {
- http.Redirect(w, r, r.Header.Get("Referer"), http.StatusSeeOther)
+ http.Redirect(w, r, "/" + *Key + "/" + board, http.StatusSeeOther)
return
}
@@ -824,13 +1020,13 @@ func main() {
if !IsIDLocal(db, id) {
CreateLocalReportDB(db, id, board, reason)
- http.Redirect(w, r, r.Header.Get("Referer"), http.StatusSeeOther)
+ http.Redirect(w, r, "/" + board + "/" + remoteShort(id), http.StatusSeeOther)
return
}
reported := ReportActivity(db, id, reason)
if reported {
- http.Redirect(w, r, r.Header.Get("Referer"), http.StatusSeeOther)
+ http.Redirect(w, r, id, http.StatusSeeOther)
return
}
@@ -859,6 +1055,48 @@ func main() {
w.Write([]byte(""))
})
+ http.HandleFunc("/.well-known/webfinger", func(w http.ResponseWriter, r *http.Request) {
+ acct := r.URL.Query()["resource"]
+
+ if(len(acct) < 1) {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte("resource needs a value"))
+ return
+ }
+
+ acct[0] = strings.Replace(acct[0], "acct:", "", -1)
+
+ actorDomain := strings.Split(acct[0], "@")
+
+ if(len(actorDomain) < 2) {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte("accpets only subject form of acct:board@instance"))
+ return
+ }
+
+ if !IsActorLocal(db, TP + "" + actorDomain[1] + "/" + actorDomain[0]) {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte("actor not local"))
+ return
+ }
+
+ var finger Webfinger
+ var link WebfingerLink
+
+ finger.Subject = "acct:" + actorDomain[0] + "@" + actorDomain[1]
+ link.Rel = "self"
+ link.Type = "application/activity+json"
+ link.Href = TP + "" + actorDomain[1] + "/" + actorDomain[0]
+
+ finger.Links = append(finger.Links, link)
+
+ enc, _ := json.Marshal(finger)
+
+ w.Header().Set("Content-Type", activitystreams)
+ w.Write(enc)
+
+ })
+
fmt.Println("Server for " + Domain + " running on port " + Port)
fmt.Println("Mod key: " + *Key)
@@ -934,11 +1172,20 @@ func CreateTripCode(input string) string {
return code[0]
}
-func CreateNameTripCode(input string) string {
+func CreateNameTripCode(r *http.Request, db *sql.DB) (string, string) {
+ input := r.FormValue("name")
re := regexp.MustCompile("#.+")
chunck := re.FindString(input)
- hash := CreateTripCode(chunck)
- return re.ReplaceAllString(input, "!" + hash[42:50])
+ ce := regexp.MustCompile(`(?i)#Admin`)
+ admin := ce.MatchString(chunck)
+ board, modcred := GetPasswordFromSession(r)
+ if(admin && HasAuth(db, modcred, board)) {
+ return re.ReplaceAllString(input, ""), "#Admin"
+ } else if(chunck != "") {
+ hash := CreateTripCode(chunck)
+ return re.ReplaceAllString(input, ""), "!" + hash[42:50]
+ }
+ return input, ""
}
func GetActorFromPath(db *sql.DB, location string, prefix string) Actor {
@@ -1029,7 +1276,7 @@ func CreateNewActor(board string, prefName string, summary string, authReq []str
actor.Name = board
}
- actor.Type = "Service"
+ actor.Type = "Group"
actor.Id = fmt.Sprintf("%s", path)
actor.Following = fmt.Sprintf("%s/following", actor.Id)
actor.Followers = fmt.Sprintf("%s/followers", actor.Id)
@@ -1104,21 +1351,22 @@ func AddFollowersToActivity(db *sql.DB, activity Activity) Activity{
func CreateActivity(activityType string, obj ObjectBase) Activity {
var newActivity Activity
-
+ actor := FingerActor(obj.Actor)
+
newActivity.AtContext.Context = "https://www.w3.org/ns/activitystreams"
newActivity.Type = activityType
newActivity.Published = obj.Published
- newActivity.Actor = obj.Actor
+ newActivity.Actor = &actor
newActivity.Object = &obj
for _, e := range obj.To {
- if obj.Actor.Id != e {
+ if obj.Actor != e {
newActivity.To = append(newActivity.To, e)
}
}
for _, e := range obj.Cc {
- if obj.Actor.Id != e {
+ if obj.Actor != e {
newActivity.Cc = append(newActivity.Cc, e)
}
}
@@ -1173,7 +1421,7 @@ func CreatePreviewObject(obj ObjectBase) *NestedObjectBase {
objFile := re.FindString(obj.Href)
- cmd := exec.Command("convert", "." + objFile ,"-resize", "250x250>", "." + href)
+ cmd := exec.Command("convert", "." + objFile ,"-resize", "250x250>", "-strip","." + href)
err := cmd.Run()
@@ -1349,36 +1597,14 @@ func GetActorCollection(collection string) Collection {
}
func IsValidActor(id string) (Actor, bool) {
- var respCollection Actor
- req, err := http.NewRequest("GET", id, nil)
-
- CheckError(err, "error with valid actor request")
- req.Header.Set("Accept", activitystreams)
+ actor := FingerActor(id)
- resp, err := http.DefaultClient.Do(req)
-
- CheckError(err, "error with valid actor response")
-
- defer resp.Body.Close()
-
- if resp.StatusCode == 403 {
- return respCollection, false;
- }
-
- body, _ := ioutil.ReadAll(resp.Body)
-
- err = json.Unmarshal(body, &respCollection)
-
- if err != nil {
- panic(err)
- }
-
- if respCollection.Id != "" && respCollection.Inbox != "" && respCollection.Outbox != "" {
- return respCollection, true;
+ if actor.Id != "" {
+ return actor, true;
}
- return respCollection, false;
+ return actor, false;
}
func IsActivityLocal(db *sql.DB, activity Activity) bool {
@@ -1588,37 +1814,84 @@ func GetActorReported(w http.ResponseWriter, r *http.Request, db *sql.DB, id str
w.Write(enc)
}
-func MakeActivityRequest(db *sql.DB, activity Activity) {
+func MakeActivityRequestOutbox(db *sql.DB, activity Activity) {
+ j, _ := json.Marshal(activity)
- j, _ := json.MarshalIndent(activity, "", "\t")
+ if activity.Actor.Outbox == "" {
+ return
+ }
- var verify Verify
+ req, err := http.NewRequest("POST", activity.Actor.Outbox, bytes.NewBuffer(j))
- verify.Board = activity.Actor.Id
- verify.Identifier = "post"
+ CheckError(err, "error with sending activity req to outbox")
- verify = GetVerificationCode(db, verify)
+ re := regexp.MustCompile("https?://(www.)?")
+
+ var instance string
+ if activity.Actor.Id == Domain {
+ instance = re.ReplaceAllString(Domain, "")
+ } else {
+ _, instance = GetActorInstance(activity.Actor.Id)
+ }
- auth := CreateTripCode(verify.Code)
+ date := time.Now().UTC().Format(time.RFC1123)
+ path := strings.Replace(activity.Actor.Outbox, instance, "", 1)
- auth = CreateTripCode(auth)
- for _, e := range activity.To {
+ path = re.ReplaceAllString(path, "")
- actor := GetActor(e)
+ sig := fmt.Sprintf("(request-target): %s %s\nhost: %s\ndate: %s", "post", path, instance, date)
+ encSig := ActivitySign(db, *activity.Actor, sig)
+ signature := fmt.Sprintf("keyId=\"%s\",headers=\"(request-target) host date\",signature=\"%s\"", activity.Actor.PublicKey.Id, encSig)
+
+ req.Header.Set("Content-Type", activitystreams)
+ req.Header.Set("Date", date)
+ req.Header.Set("Signature", signature)
+ req.Host = instance
- if actor.Inbox != "" {
- req, err := http.NewRequest("POST", actor.Inbox, bytes.NewBuffer(j))
-
- req.Header.Set("Content-Type", activitystreams)
+ _, err = http.DefaultClient.Do(req)
+
+ CheckError(err, "error with sending activity resp to")
+}
+
+func MakeActivityRequest(db *sql.DB, activity Activity) {
+
+ j, _ := json.MarshalIndent(activity, "", "\t")
+
+ for _, e := range activity.To {
+ if e != activity.Actor.Id {
- req.Header.Set("Authorization", "Basic " + auth)
+ actor := FingerActor(e)
+
+ if actor.Id != "" {
+ _, instance := GetActorInstance(actor.Id)
- CheckError(err, "error with sending activity req to")
+ if actor.Inbox != "" {
- _, err = http.DefaultClient.Do(req)
+ req, err := http.NewRequest("POST", actor.Inbox, bytes.NewBuffer(j))
- CheckError(err, "error with sending activity resp to")
+ CheckError(err, "error with sending activity req to")
+
+ date := time.Now().UTC().Format(time.RFC1123)
+ path := strings.Replace(actor.Inbox, instance, "", 1)
+
+ re := regexp.MustCompile("https?://(www.)?")
+ path = re.ReplaceAllString(path, "")
+
+ sig := fmt.Sprintf("(request-target): %s %s\nhost: %s\ndate: %s", "post", path, instance, date)
+ encSig := ActivitySign(db, *activity.Actor, sig)
+ signature := fmt.Sprintf("keyId=\"%s\",headers=\"(request-target) host date\",signature=\"%s\"", activity.Actor.PublicKey.Id, encSig)
+
+ req.Header.Set("Content-Type", activitystreams)
+ req.Header.Set("Date", date)
+ req.Header.Set("Signature", signature)
+ req.Host = instance
+
+ _, err = http.DefaultClient.Do(req)
+
+ CheckError(err, "error with sending activity resp to")
+ }
+ }
}
}
}
@@ -1635,48 +1908,24 @@ func GetCollectionFromID(id string) Collection {
resp, err := http.DefaultClient.Do(req)
if err != nil {
- CheckError(err, "could not get collection from " + id)
return nColl
}
if resp.StatusCode == 200 {
defer resp.Body.Close()
-
- body, _ := ioutil.ReadAll(resp.Body)
- err = json.Unmarshal(body, &nColl)
+ body, _ := ioutil.ReadAll(resp.Body)
- CheckError(err, "error getting collection resp from json body")
+ if len(body) > 0 {
+ err = json.Unmarshal(body, &nColl)
+ CheckError(err, "error getting collection resp from json body")
+ }
}
return nColl
}
-func GetActorFromID(id string) Actor {
- req, err := http.NewRequest("GET", id, nil)
-
- CheckError(err, "error getting actor from id req")
-
- req.Header.Set("Accept", activitystreams)
-
- resp, err := http.DefaultClient.Do(req)
-
- CheckError(err, "error getting actor from id resp")
-
- defer resp.Body.Close()
-
- body, _ := ioutil.ReadAll(resp.Body)
-
- var respCollection Collection
-
- err = json.Unmarshal(body, &respCollection)
-
- CheckError(err, "error getting actor resp from json body")
-
- return *respCollection.OrderedItems[0].Actor
-}
-
func GetConfigValue(value string) string{
file, err := os.Open("config")
@@ -1741,20 +1990,21 @@ func DeleteObjectRequest(db *sql.DB, id string) {
var nObj ObjectBase
var nActor Actor
nObj.Id = id
- nObj.Actor = &nActor
+ nObj.Actor = nActor.Id
activity := CreateActivity("Delete", nObj)
obj := GetObjectFromPath(db, id)
- activity.Actor.Id = obj.Actor.Id
-
- followers := GetActorFollowDB(db, obj.Actor.Id)
+ actor := FingerActor(obj.Actor)
+ activity.Actor = &actor
+
+ followers := GetActorFollowDB(db, obj.Actor)
for _, e := range followers {
activity.To = append(activity.To, e.Id)
}
- following := GetActorFollowingDB(db, obj.Actor.Id)
+ following := GetActorFollowingDB(db, obj.Actor)
for _, e := range following {
activity.To = append(activity.To, e.Id)
}
@@ -1766,22 +2016,22 @@ func DeleteObjectAndRepliesRequest(db *sql.DB, id string) {
var nObj ObjectBase
var nActor Actor
nObj.Id = id
- nObj.Actor = &nActor
+ nObj.Actor = nActor.Id
activity := CreateActivity("Delete", nObj)
obj := GetObjectByIDFromDB(db, id)
- activity.Actor.Id = obj.OrderedItems[0].Actor.Id
+ activity.Actor.Id = obj.OrderedItems[0].Actor
activity.Object = &obj.OrderedItems[0]
- followers := GetActorFollowDB(db, obj.OrderedItems[0].Actor.Id)
+ followers := GetActorFollowDB(db, obj.OrderedItems[0].Actor)
for _, e := range followers {
activity.To = append(activity.To, e.Id)
}
- following := GetActorFollowingDB(db, obj.OrderedItems[0].Actor.Id)
+ following := GetActorFollowingDB(db, obj.OrderedItems[0].Actor)
for _, e := range following {
activity.To = append(activity.To, e.Id)
}
@@ -1840,7 +2090,7 @@ func ResizeAttachmentToPreview(db *sql.DB) {
objFile := re.FindString(href)
if(id != "") {
- cmd := exec.Command("convert", "." + objFile ,"-resize", "250x250>", "." + nHref)
+ cmd := exec.Command("convert", "." + objFile ,"-resize", "250x250>", "-strip", "." + nHref)
err := cmd.Run()
@@ -2030,3 +2280,113 @@ func GetPathProxyType(path string) string {
return "clearnet"
}
+
+func RunDatabaseSchema(db *sql.DB) {
+ query, err := ioutil.ReadFile("databaseschema.psql")
+ CheckError(err, "could not read databaseschema.psql file")
+ if _, err := db.Exec(string(query)); err != nil {
+ CheckError(err, "could not exec databaseschema.psql")
+ }
+}
+
+func CreatedNeededDirectories() {
+ if _, err := os.Stat("./public"); os.IsNotExist(err) {
+ os.Mkdir("./public", 0755)
+ }
+
+ if _, err := os.Stat("./pem/board"); os.IsNotExist(err) {
+ os.MkdirAll("./pem/board", 0700)
+ }
+}
+
+//looks for actor with pattern of board@instance
+func FingerActor(path string) Actor{
+
+ actor, instance := GetActorInstance(path)
+
+ r := FingerRequest(actor, instance)
+
+ var nActor Actor
+
+ if r != nil && r.StatusCode == 200 {
+ defer r.Body.Close()
+
+ body, _ := ioutil.ReadAll(r.Body)
+
+ err := json.Unmarshal(body, &nActor)
+
+ CheckError(err, "error getting fingerrequet resp from json body")
+ }
+
+ return nActor
+}
+
+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)
+
+ CheckError(err, "could not get finger request from id req")
+
+ resp, err := http.DefaultClient.Do(req)
+
+ var finger Webfinger
+
+ if err != nil {
+ return resp
+ }
+
+ if resp.StatusCode == 200 {
+ defer resp.Body.Close()
+
+ body, _ := ioutil.ReadAll(resp.Body)
+
+ err := json.Unmarshal(body, &finger)
+
+ CheckError(err, "error getting fingerrequet resp from json body")
+ }
+
+ if(len(finger.Links) > 0) {
+ for _, e := range finger.Links {
+ if(e.Type == "application/activity+json"){
+ req, err := http.NewRequest("GET", e.Href, nil)
+
+ CheckError(err, "could not get finger request from id req")
+
+ req.Header.Set("Accept", activitystreams)
+
+ resp, err := http.DefaultClient.Do(req)
+ return resp
+ }
+ }
+ }
+
+ return resp
+}
+
+func GetActorInstance(path string) (string, string) {
+ re := regexp.MustCompile(`([@]?([\w\d.-_]+)[@](.+))`)
+ atFormat := re.MatchString(path)
+
+ if(atFormat) {
+ match := re.FindStringSubmatch(path)
+ if(len(match) > 2) {
+ return match[2], match[3]
+ }
+ }
+
+ re = regexp.MustCompile(`(https?:\\)?(www)?([\w\d-_.:]+)\/([\w\d-_.]+)(\/([\w\d-_.]+))?`)
+ httpFormat := re.MatchString(path)
+
+ if(httpFormat) {
+ match := re.FindStringSubmatch(path)
+ if(len(match) > 3) {
+ if match[4] == "users" {
+ return match[6], match[3]
+ }
+
+ return match[4], match[3]
+ }
+ }
+
+ return "", ""
+}
diff --git a/outboxGet.go b/outboxGet.go
index 49b7bd5..4064f0b 100644
--- a/outboxGet.go
+++ b/outboxGet.go
@@ -16,7 +16,7 @@ func GetActorOutbox(w http.ResponseWriter, r *http.Request, db *sql.DB) {
collection.TotalItems = GetObjectPostsTotalDB(db, actor)
collection.TotalImgs = GetObjectImgsTotalDB(db, actor)
- enc, _ := json.MarshalIndent(collection, "", "\t")
+ enc, _ := json.Marshal(collection)
w.Header().Set("Content-Type", activitystreams)
w.Write(enc)
@@ -45,7 +45,7 @@ func GetCollectionFromPath(db *sql.DB, path string) Collection {
CheckError(err, "error scan object into post struct from path")
- post.Actor = &actor
+ post.Actor = actor.Id
post.InReplyTo = GetInReplyToDB(db, post)
@@ -88,9 +88,9 @@ func GetObjectFromPath(db *sql.DB, path string) ObjectBase{
var previewID string
var nActor Actor
- nObj.Actor = &nActor
+ nObj.Actor = nActor.Id
- err = rows.Scan(&nObj.Id, &nObj.Name, &nObj.Content, &nObj.Type, &nObj.Published, &nObj.AttributedTo, &attachID, &previewID, &nObj.Actor.Id)
+ err = rows.Scan(&nObj.Id, &nObj.Name, &nObj.Content, &nObj.Type, &nObj.Published, &nObj.AttributedTo, &attachID, &previewID, &nObj.Actor)
CheckError(err, "error scan object into post struct from path")
diff --git a/OutboxPost.go b/outboxPost.go
index cb0f988..5772932 100644
--- a/OutboxPost.go
+++ b/outboxPost.go
@@ -10,6 +10,7 @@ import "io/ioutil"
import "os"
import "regexp"
import "strings"
+import "os/exec"
func ParseOutboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) {
@@ -31,7 +32,7 @@ func ParseOutboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) {
}
contentType, _ := GetFileContentType(f)
-
+
if(!SupportedMIMEType(contentType)) {
w.WriteHeader(http.StatusNotAcceptable)
w.Write([]byte("file type not supported"))
@@ -42,9 +43,7 @@ func ParseOutboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) {
var nObj = CreateObject("Note")
nObj = ObjectFromForm(r, db, nObj)
- var act Actor
- nObj.Actor = &act
- nObj.Actor.Id = Domain + "/" + actor.Name
+ nObj.Actor = Domain + "/" + actor.Name
nObj = WriteObjectToDB(db, nObj)
activity := CreateActivity("Create", nObj)
@@ -70,113 +69,83 @@ func ParseOutboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) {
w.Write([]byte("captcha could not auth"))
} else {
activity = GetActivityFromJson(r, db)
-
if IsActivityLocal(db, activity) {
+ if !VerifyHeaderSignature(r, *activity.Actor) {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ return
+ }
+
switch activity.Type {
case "Create":
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(""))
break
+
case "Follow":
-
var validActor bool
var validLocalActor bool
- header := r.Header.Get("Authorization")
-
- auth := strings.Split(header, " ")
-
- if len(auth) < 2 {
- w.WriteHeader(http.StatusBadRequest)
- w.Write([]byte(""))
- return
- }
-
- _, validActor = IsValidActor(activity.Object.Actor.Id)
+ validActor = (activity.Object.Actor != "")
validLocalActor = (activity.Actor.Id == actor.Id)
-
- var verify Verify
- verify.Identifier = "admin"
- verify.Board = activity.Actor.Id
-
- verify = GetVerificationCode(db, verify)
-
- code := verify.Code
- code = CreateTripCode(code)
- code = CreateTripCode(code)
-
- if code != auth[1] {
- verify.Identifier = "admin"
- verify.Board = Domain
-
- verify = GetVerificationCode(db, verify)
- code = verify.Code
- code = CreateTripCode(code)
- code = CreateTripCode(code)
- }
var rActivity Activity
- if validActor && validLocalActor && code == auth[1] || verify.Board == Domain {
+ if validActor && validLocalActor {
rActivity = AcceptFollow(activity)
SetActorFollowingDB(db, rActivity)
MakeActivityRequest(db, activity)
}
-
+
+ FollowingBoards = GetActorFollowingDB(db, Domain)
+ Boards = GetBoardCollection(db)
break
+
case "Delete":
fmt.Println("This is a delete")
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("could not process activity"))
break
+
case "Note":
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("could not process activity"))
break
case "New":
+ name := activity.Object.Alias
+ prefname := activity.Object.Name
+ summary := activity.Object.Summary
+ restricted := activity.Object.Sensitive
- header := r.Header.Get("Authorization")
-
- auth := strings.Split(header, " ")
-
- if len(auth) < 2 {
- w.WriteHeader(http.StatusBadRequest)
- w.Write([]byte(""))
- return
- }
-
- var verify Verify
- verify.Identifier = "admin"
- verify.Board = Domain
-
- verify = GetVerificationCode(db, verify)
+ actor := CreateNewBoardDB(db, *CreateNewActor(name, prefname, summary, authReq, restricted))
- code := verify.Code
- code = CreateTripCode(code)
- code = CreateTripCode(code)
-
- if code != auth[1] {
- w.WriteHeader(http.StatusBadRequest)
- w.Write([]byte(""))
- return
- }
-
- name := activity.Object.Actor.Name
- prefname := activity.Object.Actor.PreferredUsername
- summary := activity.Object.Actor.Summary
- restricted := activity.Object.Actor.Restricted
+ if actor.Id != "" {
+ var board []ObjectBase
+ var item ObjectBase
+ var removed bool = false
+
+ item.Id = actor.Id
+ for _, e := range FollowingBoards {
+ if e.Id != item.Id {
+ board = append(board, e)
+ } else {
+ removed = true
+ }
+ }
- actor := CreateNewBoardDB(db, *CreateNewActor(name, prefname, summary, authReq, restricted))
+ if !removed {
+ board = append(board, item)
+ }
- if actor.Id != "" {
- j, _ := json.Marshal(&actor)
- w.Write([]byte(j))
+ FollowingBoards = board
+ Boards = GetBoardCollection(db)
return
}
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(""))
break
+
default:
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("could not process activity"))
@@ -361,13 +330,26 @@ func ObjectFromForm(r *http.Request, db *sql.DB, obj ObjectBase) ObjectBase {
tempFile.Write(fileBytes)
+ re := regexp.MustCompile(`image/(jpe?g|png|webp)`)
+ if re.MatchString(obj.Attachment[0].MediaType) {
+ fileLoc := strings.ReplaceAll(obj.Attachment[0].Href, Domain, "")
+
+ cmd := exec.Command("exiv2", "rm", "." + fileLoc)
+
+ err := cmd.Run()
+
+ CheckError(err, "error with removing exif data from image")
+
+ }
+
obj.Preview = CreatePreviewObject(obj.Attachment[0])
}
- obj.AttributedTo = CreateNameTripCode(r.FormValue("name"))
- obj.AttributedTo = EscapeString(obj.AttributedTo)
+ obj.AttributedTo = EscapeString(r.FormValue("name"))
+ obj.TripCode = EscapeString(r.FormValue("tripcode"))
obj.Name = EscapeString(r.FormValue("subject"))
obj.Content = EscapeString(r.FormValue("comment"))
+ obj.Sensitive = (r.FormValue("sensitive") != "")
obj = ParseOptions(r, obj)
@@ -382,10 +364,9 @@ func ObjectFromForm(r *http.Request, db *sql.DB, obj ObjectBase) ObjectBase {
activity.To = append(activity.To, originalPost.Id)
}
-
if originalPost.Id != "" {
if !IsActivityLocal(db, activity) {
- id := GetActorFromID(originalPost.Id).Id
+ id := FingerActor(originalPost.Id).Id
actor := GetActor(id)
if !IsInStringArray(obj.To, actor.Id) {
obj.To = append(obj.To, actor.Id)
@@ -414,7 +395,7 @@ func ObjectFromForm(r *http.Request, db *sql.DB, obj ObjectBase) ObjectBase {
activity.To = append(activity.To, e.Id)
if !IsActivityLocal(db, activity) {
- id := GetActorFromID(e.Id).Id
+ id := FingerActor(e.Id).Id
actor := GetActor(id)
if !IsInStringArray(obj.To, actor.Id) {
obj.To = append(obj.To, actor.Id)
@@ -531,25 +512,18 @@ func CheckCaptcha(db *sql.DB, captcha string) bool {
func ParseInboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) {
activity := GetActivityFromJson(r, db)
-
- header := r.Header.Get("Authorization")
- auth := strings.Split(header, " ")
-
- if len(auth) < 2 {
+ if activity.Actor.PublicKey.Id == "" {
+ nActor := FingerActor(activity.Actor.Id)
+ activity.Actor = &nActor
+ }
+
+ if !VerifyHeaderSignature(r, *activity.Actor) {
response := RejectActivity(activity)
MakeActivityRequest(db, response)
return
}
- if !RemoteActorHasAuth(activity.Actor.Id, auth[1]) {
- if !RemoteActorHasAuth(Domain, auth[1]) {
- response := RejectActivity(activity)
- MakeActivityRequest(db, response)
- return
- }
- }
-
switch(activity.Type) {
case "Create":
for _, e := range activity.To {
@@ -567,11 +541,10 @@ func ParseInboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) {
if actor.Id != "" {
if activity.Object.Replies != nil {
for _, k := range activity.Object.Replies.OrderedItems {
- TombstoneObjectFromCache(db, k.Id)
- DeleteObject(db, k.Id)
+ TombstoneObject(db, k.Id)
}
}
- TombstoneObjectFromCache(db, activity.Object.Id)
+ TombstoneObject(db, activity.Object.Id)
break
}
}
diff --git a/static/bottom.html b/static/bottom.html
index c058b14..28750c8 100644
--- a/static/bottom.html
+++ b/static/bottom.html
@@ -1,16 +1,17 @@
{{ define "bottom" }}
-<div id="reply-box" class="popup-box" style="display: none; ">
+<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>
<form 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"></textarea>
<input id="reply-file" name="file" type="file">
- <input id="reply-submit" type="submit" value="Reply" style="float: right;">
+ <input id="reply-submit" type="submit" value="Reply" style="float: right;"><br><br>
<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="checkbox" name="sensitive"><span>Mark attachment as sensitive</span><br><br>
<div style="width: 202px; margin: 0 auto; padding-top: 12px;">
<label for="captcha">Captcha:</label><br>
<input style="display: inline-block;" type="text" id="captcha" name="captcha" autocomplete="off"><br>
@@ -29,7 +30,7 @@
<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="boardName" value="{{ .Board.Name }}">
+ <input type="hidden" id="boardName" name="board" value="{{ .Board.Name }}">
<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;">
diff --git a/static/faq.html b/static/faq.html
index 66c053d..4398466 100644
--- a/static/faq.html
+++ b/static/faq.html
@@ -20,7 +20,7 @@
<p>click on "No." next to a post to view its thread.</p>
<h4>Uploading files</h4>
- <p>max file size is 7MB. the supported file types are "image/gif","image/jpeg","image/png","image/svg+xml","image/webp","image/avif","image/apng","video/mp4","video/ogg","video/webm","audio/mpeg","audio/ogg","audio/wav", "audio/wave", "audio/x-wav". these were choosen based on browser support for embeding.</p>
+ <p>max file size is 7MB. the supported file types are "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". these were choosen based on browser support for embeding.</p>
<h4>JavaScript why?</h4>
<p>a version of the client with no javascript will be made eventually. current version requires it, because of basic functionality needed. no libraries or frameworks for javascript is used besides ECMAScript, just basic selection of DOM elements and modifying their styling. maybe someone would be willing to make a client that uses no javascript.</p>
diff --git a/static/index.html b/static/index.html
index 2840a83..1cd206b 100644
--- a/static/index.html
+++ b/static/index.html
@@ -1,4 +1,19 @@
{{ define "header" }}
+<title>{{ .Title }}</title>
+<meta name="description" content="{{ .Message }}">
+
+<meta property="og:locale" content="en_US" />
+<meta property="og:type" content="website" />
+<meta property="og:url" content="{{ .Board.Domain }}">
+<meta property="og:site_name" content="{{ .Board.Actor.PreferredUsername }}" />
+
+<meta property="og:title" content="{{ .Title }}">
+<meta property="og:description" content="{{ .Message }}">
+
+<meta name="twitter:title" content="{{ .Title }}">
+<meta name="twitter:description" content="{{ .Message }}">
+<meta name="twitter:card" content="summary_large_image">
+
{{ end }}
{{ define "top" }}{{ end }}
diff --git a/static/js/footerscript.js b/static/js/footerscript.js
index b68c3b4..a63f422 100644
--- a/static/js/footerscript.js
+++ b/static/js/footerscript.js
@@ -3,6 +3,10 @@ var imgArray = [].slice.call(imgs);
imgArray.forEach(function(img, i){
img.addEventListener("click", function(e){
+ var id = img.getAttribute("id")
+ var media = document.getElementById("media-" + id)
+ var sensitive = document.getElementById("sensitive-" + id)
+
if(img.getAttribute("enlarge") == "0")
{
var attachment = img.getAttribute("attachment")
diff --git a/static/js/posts.js b/static/js/posts.js
index 592a35d..06aab77 100644
--- a/static/js/posts.js
+++ b/static/js/posts.js
@@ -26,6 +26,8 @@ function shortURL(actorName, url)
{
re = /.+\//g;
temp = re.exec(url)
+
+ var output
if(stripTransferProtocol(temp[0]) == stripTransferProtocol(actorName) + "/")
{
@@ -39,11 +41,8 @@ function shortURL(actorName, url)
re = /\w+$/g;
- u = re.exec(short);
-
- return u;
+ output = re.exec(short);
}else{
-
var short = url.replace("https://", "");
short = short.replace("http://", "");
short = short.replace("www.", "");
@@ -64,10 +63,10 @@ function shortURL(actorName, url)
v = re.exec(str)
- v = "f" + v[0] + "-" + u
-
- return v;
+ output = "f" + v[0] + "-" + u
}
+
+ return output
}
function shortImg(url)
@@ -113,7 +112,7 @@ function getBoardId(url)
function convertContent(actorName, content, opid)
{
- var re = /(>>)(https:\/\/|http:\/\/)?(www\.)?.+\/\w+/gm;
+ var re = /(>>)(https?:\/\/)?(www\.)?.+\/\w+/gm;
var match = content.match(re);
var newContent = content;
if(match)
@@ -125,23 +124,60 @@ function convertContent(actorName, content, opid)
{
isOP = " (OP)";
}
-
- newContent = newContent.replace(quote, '<a class="reply" title="' + link + '" href="'+ (actorName) + "/" + shortURL(actorName, opid) + '#' + shortURL(actorName, link) + '";">>>' + shortURL(actorName, link) + isOP + '</a>');
+
+ var q = link
+
+ if(document.getElementById(link + "-content") != null) {
+ q = document.getElementById(link + "-content").innerText;
+ q = q.replaceAll('>', '/\>')
+ q = q.replaceAll('"', '')
+ q = q.replaceAll("'", "")
+ }
+ newContent = newContent.replace(quote, '<a class="reply" title="' + q + '" href="'+ (actorName) + "/" + shortURL(actorName, opid) + '#' + shortURL(actorName, link) + '";">>>' + shortURL(actorName, link) + isOP + '</a>');
})
}
- re = /^>.+/gm;
+ re = /^(\s+)?>.+/gm;
match = newContent.match(re);
if(match)
{
match.forEach(function(quote, i) {
+
newContent = newContent.replace(quote, '<span class="quote">' + quote + '</span>');
})
}
- return newContent
+ return newContent.replaceAll('/\>', '>')
+}
+
+function convertContentNoLink(actorName, content, opid)
+{
+ var re = /(>>)(https?:\/\/)?(www\.)?.+\/\w+/gm;
+ var match = content.match(re);
+ var newContent = content;
+ if(match)
+ {
+ match.forEach(function(quote, i){
+ var link = quote.replace('>>', '')
+ var isOP = ""
+ if(link == opid)
+ {
+ isOP = " (OP)";
+ }
+
+ var q = link
+
+ if(document.getElementById(link + "-content") != null) {
+ q = document.getElementById(link + "-content").innerText;
+ }
+
+ newContent = newContent.replace(quote, '>>' + shortURL(actorName, link) + isOP);
+ })
+ }
+ newContent = newContent.replaceAll("'", "")
+ return newContent.replaceAll('"', '')
}
function closeReply()
@@ -184,11 +220,11 @@ function quote(actorName, opid, id)
if(id == "reply") {
var h = document.getElementById(id + "-content").offsetTop - 548;
} else {
- var h = document.getElementById(id + "-content").offsetTop - 448;
+ var h = document.getElementById(id + "-content").offsetTop - 348;
}
- box.setAttribute("style", "display: block; position: absolute; width: 400px; height: 550px; z-index: 9; top: " + h + "px; left: " + w + "px; padding: 5px;");
+ box.setAttribute("style", "display: block; position: absolute; width: 400px; height: 600px; z-index: 9; top: " + h + "px; left: " + w + "px; padding: 5px;");
if (inReplyTo.value != opid)
@@ -212,7 +248,7 @@ function report(actorName, id)
var inReplyTo = document.getElementById("report-inReplyTo-box");
var w = window.innerWidth / 2 - 200;
- var h = document.getElementById(id + "-content").offsetTop - 448;
+ var h = document.getElementById(id + "-content").offsetTop - 348;
box.setAttribute("style", "display: block; position: absolute; width: 400px; height: 480px; z-index: 9; top: " + h + "px; left: " + w + "px; padding: 5px;");
diff --git a/static/main.html b/static/main.html
index 5c39a40..91d8344 100644
--- a/static/main.html
+++ b/static/main.html
@@ -1,10 +1,13 @@
{{ define "layout" }}
<!DOCTYPE html>
<html>
- <meta name="viewport" content="width=device-width, initial-scale=1">
<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <meta name="keywords" content="Federated Imageboard based on Activtypub">
+ <meta property="og:locale" content="en_US" />
+ <meta property="og:type" content="website" />
<link rel="icon" type="image/png" href="/static/favicon.png">
- <title>{{ .Title }}</title>
<style>
a, a:link, a:visited, a:hover, a:active {
text-decoration: none
@@ -19,7 +22,7 @@
}
body {
- {{ if .Board.Post.Actor.Restricted }}
+ {{ if .Board.Restricted }}
background-color: #eef2fe;
color: black;
{{ else }}
@@ -29,7 +32,7 @@
}
.popup-box {
- {{ if .Board.Post.Actor.Restricted }}
+ {{ if .Board.Restricted }}
border: 4px solid #d3caf0;
background-color: #eff5ff;
{{ else }}
@@ -43,7 +46,7 @@
}
.reply {
- {{ if .Board.Post.Actor.Restricted }}
+ {{ if .Board.Restricted }}
color:#af0a0f;
{{ else }}
color:#000080;
@@ -51,7 +54,7 @@
}
.post {
- {{ if .Board.Post.Actor.Restricted }}
+ {{ if .Board.Restricted }}
background-color: #d5daf0;
{{ else }}
background-color: #f0e0d6;
@@ -59,12 +62,15 @@
}
:target > div > .post {
- {{ if .Board.Post.Actor.Restricted }}
+ {{ if .Board.Restricted }}
background-color: #d6bad0;
{{ else }}
background-color: #f0c0b0;
{{ end }}
}
+ .tripcode {
+ color: #117743;
+ }
</style>
{{ template "header" . }}
</head>
diff --git a/static/manage.html b/static/manage.html
index 570c0f5..4fb417f 100644
--- a/static/manage.html
+++ b/static/manage.html
@@ -9,10 +9,10 @@
<!-- <div><a href="/{{ .Key }}/deleteboard?name={{ .Board.Name }}">[Delete Board]</a></div> -->
<ul style="display: inline-block; padding: 0;">
{{ if .IsLocal }}
- <li style="display: inline-block;"><a href="javascript:show('following')">[ Subscribed ]</a></li>
- <li style="display: inline-block;"><a href="javascript:show('followers')">[ Subscribers ]</a></li>
+ <li style="display: inline-block;"><a href="#following">[ Subscribed ]</a></li>
+ <li style="display: inline-block;"><a href="#followers">[ Subscribers ]</a></li>
{{ end }}
- <li style="display: inline-block;"><a href="javascript:show('reported')">[ Reported ]</a></li>
+ <li style="display: inline-block;"><a href="#reported">[ Reported ]</a></li>
</ul>
</div>
<a href="/{{ .Board.Name }}">[Return]</a>
@@ -20,25 +20,24 @@
{{ $board := .Board }}
{{ $key := .Key }}
{{ if .IsLocal }}
-<div id="following">
- <h4>Subscribed</h4>
+
+<div id="following" class="popup-box" style="margin-bottom: 25px; margin-top: 5px; padding: 12px;">
+ <h4 style="margin: 0; margin-bottom: 5px;">Subscribed</h4>
<form id="follow-form" action="/{{ .Key }}/{{ .Board.Name }}/follow" method="post" enctype="application/x-www-form-urlencoded">
- <label>Subscribe:</label><br>
- <input id="follow" name="follow" style="margin-bottom: 12px;" placeholder="https://server.fchan.xyz/g"></input>
+ <input id="follow" name="follow" style="margin-bottom: 12px;" placeholder="https://fchan.xyz/g"></input>
<input type="submit" value="Subscribe"><br>
<input type="hidden" name="actor" value="{{ $board.Actor.Id }}">
- </form>
- <ul style="display: inline-block; padding: 0; margin: 0;">
-
+ </form>
+ <ul style="display: inline-block; padding: 0; margin: 0; list-style-type: none;">
{{ range .Following }}
<li><a href="/{{ $key }}/{{ $board.Name }}/follow?follow={{ . }}&actor={{ $actor }}">[Unsubscribe]</a><a href="{{ . }}">{{ . }}</a></li>
{{ end }}
</ul>
</div>
-<div id="followers" style="display: none;">
- <h4>Subscribers</h4>
- <ul style="display: inline-block; padding: 0; margin: 0;">
+<div id="followers" class="popup-box" style="margin-bottom: 25px; padding: 12px;">
+ <h4 style="margin: 0; margin-bottom: 5px;">Subscribers</h4>
+ <ul style="display: inline-block; padding: 0; margin: 0; list-style-type: none;">
{{ range .Followers }}
<li><a href="{{ . }}">{{ . }}</a></li>
{{ end }}
@@ -46,13 +45,12 @@
</div>
{{ end }}
-<div id="reported" style="display: none;">
- <h4>Reported</h4>
- <ul style="display: inline-block; padding: 0; margin: 0;">
-
+<div id="reported" class="popup-box" 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 }}
{{ range .Reported }}
- <li><a id="rpost" post="{{ .ID }}" href=""></a> - <b>{{ .Count }}</b> <a href="/delete?id={{ .ID }}&board={{ $board.Name }}">[Remove Post]</a> <a href="/deleteattach?id={{ .ID }}">[Remove Attachment]</a> <a href="/report?id={{ .ID }}&close=1&board={{ $board.Name }}">[Close]</a></li>
+ <li><a id="rpost" post="{{ .ID }}" href=""></a> - <b>{{ .Count }}</b> <a href="/delete?id={{ .ID }}&board={{ $board.Name }}&manage=t">[Remove Post]</a> <a href="/deleteattach?id={{ .ID }}&board={{ $board.Name }}&manage=t">[Remove Attachment]</a> <a href="/report?id={{ .ID }}&close=1&board={{ $board.Name }}">[Close]</a></li>
{{ end }}
</ul>
</div>
@@ -61,21 +59,6 @@
{{ define "script" }}
<script>
- function show(element)
- {
- {{ if .IsLocal }}
- var following = document.getElementById("following");
- var followers = document.getElementById("followers");
- following.style.display = "none";
- followers.style.display = "none";
- {{ end }}
-
- var reported = document.getElementById("reported");
- reported.style.display = "none";
-
- document.getElementById(element).style.display = "block";
- }
-
var reported = document.querySelectorAll('#rpost');
var reportedArray = [].slice.call(reported);
diff --git a/static/nadmin.html b/static/nadmin.html
index 0583efc..984eb76 100644
--- a/static/nadmin.html
+++ b/static/nadmin.html
@@ -19,20 +19,19 @@
</form>
<ul style="display: inline-block; padding: 0;">
- <li style="display: inline-block;"><a href="javascript:show('following')">Subscribed</a></li>
+ <li style="display: inline-block;"><a href="#following">Subscribed</a></li>
<!-- <li style="display: inline-block;"><a href="javascript:show('followers')">Followers</a></li> -->
- <li style="display: inline-block;"><a href="javascript:show('reported')">Reported</a></li>
+ <li style="display: inline-block;"><a href="#reported">Reported</a></li>
</ul>
</div>
-<div id="following">
- <h4>Following</h4>
+<div id="following" class="popup-box" 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">
- <label>Subscribe:</label><br>
<input id="follow" name="follow" style="margin-bottom: 12px;" placeholder="http://localhost:3000/g"></input><input type="submit" value="Subscribe"><br>
<input type="hidden" name="actor" value="{{ .Actor }}">
</form>
- <ul style="display: inline-block; padding: 0; margin: 0;">
+ <ul style="display: inline-block; padding: 0; margin: 0; list-style-type: none;">
{{ $actor := .Actor }}
{{ $key := .Key }}
{{ range .Following }}
@@ -41,36 +40,22 @@
</ul>
</div>
-<div id="followers" style="display: none;">
- <h4>Followers</h4>
- <ul style="display: inline-block; padding: 0; margin: 0;">
+<div id="followers" class="popup-box" 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 }}
<li><a href="http://localhost:3000/g">{{ . }}</a></li>
{{ end }}
</ul>
</div>
-<div id="reported" style="display: none;">
- <h4>Reported</h4>
- <ul style="display: inline-block; padding: 0; margin: 0;">
+<div id="reported" class="popup-box" 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;">
</ul>
</div>
{{ end }}
{{ define "bottom" }}{{ end }}
{{ define "script" }}
-<script>
- function show(element)
- {
- var following = document.getElementById("following");
- // var followers = document.getElementById("followers");
- var reported = document.getElementById("reported");
-
- following.style.display = "none";
- // followers.style.display = "none";
- reported.style.display = "none";
-
- document.getElementById(element).style.display = "block";
- }
-</script>
{{ end }}
diff --git a/static/ncatalog.html b/static/ncatalog.html
index 81d31f3..deb8e27 100644
--- a/static/ncatalog.html
+++ b/static/ncatalog.html
@@ -1,4 +1,16 @@
{{ define "header" }}
+<title>/{{ .Board.Name }}/ - catalog</title>
+<meta name="description" content="{{ .Board.Summary }}">
+<meta property="og:url" content="{{ .Board.Actor.Id }}">
+<meta property="og:site_name" content="{{ .Instance.PreferredUsername }}" />
+
+<meta property="og:title" content="{{ .Title }}">
+<meta property="og:description" content="{{ .Board.Summary }}">
+
+<meta name="twitter:title" content="{{ .Title }}">
+<meta name="twitter:description" content="{{ .Board.Summary }}">
+<meta name="twitter:card" content="summary_large_image">
+
<script src="/static/js/posts.js"></script>
{{ end }}
@@ -16,19 +28,36 @@
{{ range .Posts }}
<div style="overflow: hidden; vertical-align: top; padding-right: 24px; padding-bottom: 24px; display: inline-block; width: 180px; max-height: 320px; margin-bottom: 10px;">
{{ if eq $board.ModCred $board.Domain $board.Actor.Id }}
- <a href="/delete?id={{ .Id }}">[Delete Post]</a>
+ <a href="/delete?id={{ .Id }}&board={{ $board.Actor.Name }}">[Delete Post]</a>
{{ end }}
{{ if .Attachment }}
{{ if eq $board.ModCred $board.Domain $board.Actor.Id }}
- <a href="/deleteattach?id={{ .Id }}">[Delete Attachment]</a>
+ <a href="/deleteattach?id={{ .Id }}&board={{ $board.Actor.Name }}">[Delete Attachment]</a>
+ <a href="/marksensitive?id={{ .Id }}&board={{ $board.Actor.Name }}">[Mark Sensitive]</a>
{{ end }}
- <a id="{{ .Id }}-anchor" href="/{{ $board.Name }}/">
+
+ {{ if and .Sensitive $board.Actor.Restricted }}
+ <div id="hide-{{ .Id }}" style="display: none;">[Hide]</div>
+ <div id="sensitive-{{ .Id }}" style="display: none;"><div style="position: relative; text-align: center;"><img style="float: left; margin-right: 10px; margin-bottom: 10px; max-width: 180px; max-height: 180px;" src="/static/sensitive.png"><div style="width: 170px; position: absolute; margin-top: 75px; padding: 5px; background-color: black; color: white; cursor: default; ">NSFW Content</div></div></div>
+ {{ end }}
+ <a id="{{ .Id }}-anchor" href="/{{ $board.Name }}/">
<div id="media-{{ .Id }}" style="width:180px;"></div>
+ </a>
<script>
media = document.getElementById("media-{{ .Id }}")
+
+ if({{ .Sensitive }} && {{ $board.Actor.Restricted }}){
+ sensitive = document.getElementById("sensitive-{{ .Id }}")
+ hide = document.getElementById("hide-{{ .Id }}")
+ sensitive.onclick = function(){document.getElementById("media-{{ .Id }}").style="display: block;"; document.getElementById("sensitive-{{ .Id }}").style="display: none;"; document.getElementById("hide-{{ .Id }}").style="display: block; cursor: pointer;"}
+ hide.onclick = function(){document.getElementById("media-{{ .Id }}").style="display: none;"; document.getElementById("sensitive-{{ .Id }}").style="display: block;"; document.getElementById("hide-{{ .Id }}").style="display: none;"}
+ sensitive.style = "display: block"
+ media.style = "display: none;"
+ }
+
if(getMIMEType({{ (index .Attachment 0).MediaType }}) == "image"){
var img = document.createElement("img");
- img.style = "max-width: 180px; max-height: 180px; cursor: move;"
+ img.style = "max-width: 180px; max-height: 180px;"
img.setAttribute("id", "img")
img.setAttribute("main", "1")
img.setAttribute("src", "{{ (index .Attachment 0).Href }}")
@@ -56,9 +85,12 @@
video.style = "margin-right: 10px; margin-bottom: 10px; max-width: 180px; max-height: 180px;"
video.innerText = 'Video is not supported.'
media.appendChild(video)
- }
+ }
+
+ document.getElementById("{{ .Id }}-anchor").href = "/{{ $board.Name }}/" + shortURL("{{$board.Actor.Id}}", "{{ .Id }}")
</script>
{{ end }}
+ <a id="{{ .Id }}-link" href="/{{ $board.Name }}/">
<div>
{{ $replies := .Replies }}
{{ if $replies }}
@@ -71,12 +103,11 @@
{{ if .Content }}
<span style="display: block">{{.Content}}</span>
{{ end }}
-
</div>
- </a>
+ </a>
</div>
<script>
- document.getElementById("{{ .Id }}-anchor").href = "/{{ $board.Name }}/" + shortURL("{{$board.Actor.Id}}", "{{ .Id }}")
+ document.getElementById("{{ .Id }}-link").href = "/{{ $board.Name }}/" + shortURL("{{$board.Actor.Id}}", "{{ .Id }}")
</script>
{{ end }}
</div>
diff --git a/static/notfound.png b/static/notfound.png
new file mode 100644
index 0000000..1253e47
--- /dev/null
+++ b/static/notfound.png
Binary files differ
diff --git a/static/npost.html b/static/npost.html
index b91b795..740018b 100644
--- a/static/npost.html
+++ b/static/npost.html
@@ -1,4 +1,21 @@
{{ define "header" }}
+<title>/{{ .Board.Name }}/ - {{ .PostId }}</title>
+<meta name="description" content="{{ (index .Posts 0).Content }}">
+<meta property="og:url" content="{{ (index .Posts 0).Id }}">
+<meta property="og:site_name" content="{{ .Instance.PreferredUsername }}" />
+
+<meta property="og:title" content="{{ (index .Posts 0).Name }}">
+<meta property="og:description" content="{{ (index .Posts 0).Content }}">
+
+<meta name="twitter:title" content="{{ (index .Posts 0).Name }}">
+<meta name="twitter:description" content="{{ (index .Posts 0).Content }}">
+<meta name="twitter:card" content="summary_large_image">
+
+{{ if (index .Posts 0).Preview }}
+<meta property="og:image" content="{{ (index .Posts 0).Preview.Href }}" />
+<meta name="twitter:image" content="{{ (index .Posts 0).Preview.Href }}" />
+{{ end }}
+
<script src="/static/js/posts.js"></script>
{{ end }}
diff --git a/static/nposts.html b/static/nposts.html
index 173f175..d106f74 100644
--- a/static/nposts.html
+++ b/static/nposts.html
@@ -1,4 +1,15 @@
{{ define "header" }}
+<title>{{ .Title }}</title>
+<meta name="description" content="{{ .Board.Summary }}">
+<meta property="og:url" content="{{ .Board.Actor.Id }}">
+<meta property="og:site_name" content="{{ .Instance.PreferredUsername }}" />
+
+<meta property="og:title" content="{{ .Title }}">
+<meta property="og:description" content="{{ .Board.Summary }}">
+
+<meta name="twitter:title" content="{{ .Title }}">
+<meta name="twitter:description" content="{{ .Board.Summary }}">
+<meta name="twitter:card" content="summary_large_image">
<script src="/static/js/posts.js"></script>
{{ end }}
diff --git a/static/posts.html b/static/posts.html
index a32d7c5..1b1d78b 100644
--- a/static/posts.html
+++ b/static/posts.html
@@ -9,19 +9,32 @@
<div style="overflow: auto;">
<div id="{{ .Id }}" style="overflow: visible; margin-bottom: 12px;">
{{ if eq $board.ModCred $board.Domain $board.Actor.Id }}
- <a href="/delete?id={{ .Id }}">[Delete Post]</a>
+ <a href="/delete?id={{ .Id }}&board={{ $board.Actor.Name }}">[Delete Post]</a>
{{ end }}
{{ if .Attachment }}
{{ if eq $board.ModCred $board.Domain $board.Actor.Id }}
- <a href="/deleteattach?id={{ .Id }}">[Delete Attachment]</a>
+ <a href="/deleteattach?id={{ .Id }}&board={{ $board.Actor.Name }}">[Delete Attachment]</a>
+ <a href="/marksensitive?id={{ .Id }}&board={{ $board.Actor.Name }}">[Mark Sensitive]</a>
{{ end }}
<span style="display: block;">File: <a id="{{ .Id }}-img" href="{{ (index .Attachment 0).Href}}">{{ (index .Attachment 0).Name }}</a><span id="{{ .Id }}-size">({{ (index .Attachment 0).Size }})</span></span>
+ <div id="hide-{{ .Id }}" style="display: none;">[Hide]</div>
+ <div id="sensitive-{{ .Id }}" style="display: none;"><div style="position: relative; text-align: center;"><img style="float: left; margin-right: 10px; margin-bottom: 10px; max-width: 250px; max-height: 250px;" src="/static/sensitive.png"><div style="width: 240px; position: absolute; margin-top: 110px; padding: 5px; background-color: black; color: white; cursor: default; ">NSFW Content</div></div></div>
<div id="media-{{ .Id }}"></div>
<script>
media = document.getElementById("media-{{ .Id }}")
+
+ if({{ .Sensitive }} && {{ $board.Actor.Restricted }}){
+ sensitive = document.getElementById("sensitive-{{ .Id }}")
+ hide = document.getElementById("hide-{{ .Id }}")
+ sensitive.onclick = function(){document.getElementById("media-{{ .Id }}").style="display: block;"; document.getElementById("sensitive-{{ .Id }}").style="display: none;"; document.getElementById("hide-{{ .Id }}").style="display: block; cursor: pointer;"}
+ hide.onclick = function(){document.getElementById("media-{{ .Id }}").style="display: none;"; document.getElementById("sensitive-{{ .Id }}").style="display: block;"; document.getElementById("hide-{{ .Id }}").style="display: none;"}
+ sensitive.style = "display: block"
+ media.style = "display: none;"
+ }
+
if(getMIMEType({{ (index .Attachment 0).MediaType }}) == "image"){
var img = document.createElement("img");
- img.style = "float: left; margin-right: 10px; margin-bottom: 10px; max-width: 250px; max-height: 250px; cursor: pointer;"
+ img.style = "float: left; margin-right: 10px; margin-bottom: 10px; max-width: 250px; max-height: 250px;"
img.setAttribute("id", "img")
img.setAttribute("main", "1")
img.setAttribute("enlarge", "0")
@@ -60,7 +73,7 @@
}
</script>
{{ end }}
- <span style="color: #0f0c5d;"><b>{{ .Name }}</b></span><span style="color: #117743;"><b>{{ if .AttributedTo }} {{.AttributedTo }} {{ else }} Anonymous {{ end }}</b></span><span>{{ .Published }} <a id="{{ .Id }}-anchor" href="/{{ $board.Name }}/">No.</a> <a id="{{ .Id }}-link" title="{{ .Id }}" href="javascript:quote('{{ $board.Actor.Id }}', '{{ $opId }}', '{{ .Id }}')">{{ .Id }}</a> {{ if ne .Type "Tombstone" }}<a href="javascript:report('{{ $board.Actor.Id }}', '{{ .Id }}')">[Report]</a>{{ end }}</span>
+ <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 }}/">No.</a> <a id="{{ .Id }}-link" title="{{ .Id }}" href="javascript:quote('{{ $board.Actor.Id }}', '{{ $opId }}', '{{ .Id }}')">{{ .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;">{{.Content}}</p>
{{ if .Replies }}
{{ $replies := .Replies }}
@@ -75,26 +88,42 @@
<div style="float: left; display: block; margin-right: 5px;">>></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 }}">[Delete Post]</a>
+ <a href="/delete?id={{ .Id }}&board={{ $board.Actor.Name }}">[Delete Post]</a>
{{ end }}
{{ if .Attachment }}
{{ if eq $board.ModCred $board.Domain $board.Actor.Id }}
- <a href="/deleteattach?id={{ .Id }}">[Delete Attachment]</a>
+ <a href="/deleteattach?id={{ .Id }}&board={{ $board.Actor.Name }}">[Delete Attachment]</a>
+ <a href="/marksensitive?id={{ .Id }}&board={{ $board.Actor.Name }}">[Mark Sensitive]</a>
{{ end }}
<span style="display: block;">File <a id="{{ .Id }}-img" href="{{ (index .Attachment 0).Href}}">{{ (index .Attachment 0).Name }}</a> <span id="{{ .Id }}-size">({{ (index .Attachment 0).Size }})</span></span>
- <div id="media-{{ .Id }}"></div>
+ {{ if and .Sensitive $board.Actor.Restricted }}
+ <div id="hide-{{ .Id }}" style="display: none;">[Hide]</div>
+ <div id="sensitive-{{ .Id }}" style="display: none;"><div style="position: relative; text-align: center;"><img style="float: left; margin-right: 10px; margin-bottom: 10px; max-width: 250px; max-height: 250px;" src="/static/sensitive.png"><div style="width: 240px; position: absolute; margin-top: 110px; padding: 5px; background-color: black; color: white; cursor: default; ">NSFW Content</div></div></div>
+ {{ end }}
+ <div id="media-{{ .Id }}" sensitive="0"></div>
<script>
media = document.getElementById("media-{{ .Id }}")
+
+ if({{ .Sensitive }} && {{ $board.Actor.Restricted }}){
+ sensitive = document.getElementById("sensitive-{{ .Id }}")
+ hide = document.getElementById("hide-{{ .Id }}")
+ sensitive.onclick = function(){document.getElementById("media-{{ .Id }}").style="display: block;"; document.getElementById("sensitive-{{ .Id }}").style="display: none;"; document.getElementById("hide-{{ .Id }}").style="display: block; cursor: pointer;"}
+ hide.onclick = function(){document.getElementById("media-{{ .Id }}").style="display: none;"; document.getElementById("sensitive-{{ .Id }}").style="display: block;"; document.getElementById("hide-{{ .Id }}").style="display: none;"}
+ sensitive.style = "display: block"
+ media.style = "display: none;"
+ }
+
if(getMIMEType({{ (index .Attachment 0).MediaType }}) == "image"){
var img = document.createElement("img");
- img.style = "float: left; margin-right: 10px; margin-bottom: 10px; max-width: 250px; max-height: 250px; cursor: pointer;"
+ img.style = "float: left; margin-right: 10px; margin-bottom: 10px; max-width: 250px; max-height: 250px;"
img.setAttribute("id", "img")
img.setAttribute("main", "1")
img.setAttribute("enlarge", "0")
img.setAttribute("attachment", "{{ (index .Attachment 0).Href }}")
- {{ if .Preview.Href }}
+ img.setAttribute("post", "{{ .Id }}")
+ {{ if and .Preview.Href . }}
img.setAttribute("src", "{{ .Preview.Href }}")
- img.setAttribute("preview", "{{ .Preview.Href }}")
+ img.setAttribute("preview", "{{ .Preview.Href }}")
{{ else }}
img.setAttribute("src", "{{ (index .Attachment 0).Href }}")
img.setAttribute("preview", "{{ (index .Attachment 0).Href }}")
@@ -107,7 +136,7 @@
audio.controls = 'controls'
audio.preload = 'metadata'
audio.src = '{{ (index .Attachment 0).Href }}'
- audio.type = '{{ (index .Attachment 0).MediaType }}'
+ audio.type = '{{ (index .Attachment 0).MediaType }}'
audio.style = "float: left; margin-right: 10px; margin-bottom: 10px; max-width: 250px; max-height: 250px;"
audio.innerText = 'Audio is not supported.'
media.appendChild(audio)
@@ -119,22 +148,25 @@
video.preload = 'metadata'
video.muted = 'muted'
video.src = '{{ (index .Attachment 0).Href }}'
- video.type = '{{ (index .Attachment 0).MediaType }}'
+ video.type = '{{ (index .Attachment 0).MediaType }}'
video.style = "float: left; margin-right: 10px; margin-bottom: 10px; max-width: 250px; max-height: 250px;"
video.innerText = 'Video is not supported.'
media.appendChild(video)
}
</script>
{{ end }}
- <span style="color: #0f0c5d;"><b>{{ .Name }}</b></span><span style="color: #117743;"><b>{{ if .AttributedTo }} {{.AttributedTo }} {{ else }} Anonymous {{ end }}</b></span><span>{{ .Published }} <a id="{{ .Id }}-anchor" href="/{{ $board.Name }}/post/{{ $opId }}#{{ .Id }}">No. </a><a id="{{ .Id }}-link" title="{{ .Id }}" href="javascript:quote('{{ $board.Actor.Id }}', '{{ $opId }}', '{{ .Id }}')">{{ .Id }}</a> {{ if ne .Type "Tombstone" }}<a href="javascript:report('{{ $board.Actor.Id }}', '{{ .Id }}')">[Report]</a>{{ end }}</span>
+ <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 }}/post/{{ $opId }}#{{ .Id }}">No. </a><a id="{{ .Id }}-link" title="{{ .Id }}" href="javascript:quote('{{ $board.Actor.Id }}', '{{ $opId }}', '{{ .Id }}')">{{ .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 }}
<span id="{{$parentId}}-replyto-{{.Id}}"></span>
- <script>document.getElementById("{{ $parentId }}-replyto-{{.Id}}").innerHTML = "<a title='{{ .Id }}' href='/{{ $board.Name }}/" + shortURL("{{ $board.Actor.Id }}", "{{ $opId }}") + "#" + shortURL("{{ $board.Actor.Id }}", "{{ .Id }}") + "'>>>" + shortURL("{{ $board.Actor.Id }}", "{{ .Id }}") + "</a>";</script>
- {{ end }}
- {{ end }}
- <p id="{{ .Id }}-content" style="white-space: pre-wrap; margin: 10px 30px 10px 30px;">{{.Content}}</p>
+ <script>
+ var content = convertContentNoLink('{{$board.Actor.Id}}', '{{ .Content }}', '{{ $opId }}')
+ document.getElementById("{{ $parentId }}-replyto-{{.Id}}").innerHTML = "<a title='" + content +"' href='/{{ $board.Name }}/" + shortURL("{{ $board.Actor.Id }}", "{{ $opId }}") + "#" + shortURL("{{ $board.Actor.Id }}", "{{ .Id }}") + "'>>>" + shortURL("{{ $board.Actor.Id }}", "{{ .Id }}") + "</a>";
+ </script>
+ {{ end }}
+ {{ end }}
+ <p id="{{ .Id }}-content" style="white-space: pre-wrap; margin: 10px 30px 10px 30px;">{{.Content}}</p>
</div>
</div>
</div>
diff --git a/static/sensitive.png b/static/sensitive.png
new file mode 100644
index 0000000..ea706c8
--- /dev/null
+++ b/static/sensitive.png
Binary files differ
diff --git a/static/top.html b/static/top.html
index b891e14..e2612e8 100644
--- a/static/top.html
+++ b/static/top.html
@@ -25,6 +25,7 @@
<input type="hidden" id="boardName" name="boardName" value="{{ .Board.Name }}">
<input type="hidden" id="captchaCode" name="captchaCode" value="{{ .Board.CaptchaCode }}">
<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>
diff --git a/verification.go b/verification.go
index 97f80bf..db44689 100644
--- a/verification.go
+++ b/verification.go
@@ -8,6 +8,17 @@ import "time"
import "os/exec"
import "os"
import "math/rand"
+import "crypto"
+import "crypto/rsa"
+import "crypto/x509"
+import "crypto/sha256"
+import "encoding/pem"
+import "encoding/base64"
+import crand "crypto/rand"
+import "io/ioutil"
+import "strings"
+import "net/http"
+import "regexp"
type Verify struct {
Type string
@@ -23,6 +34,13 @@ type VerifyCooldown struct {
Time int
}
+type Signature struct {
+ KeyId string
+ Headers []string
+ Signature string
+ Algorithm string
+}
+
func DeleteBoardMod(db *sql.DB, verify Verify) {
query := `select code from boardaccess where identifier=$1 and board=$1`
@@ -330,8 +348,6 @@ func HasAuth(db *sql.DB, code string, board string) bool {
return true
}
- fmt.Println("has auth is false")
-
return false;
}
@@ -473,4 +489,222 @@ func Captcha() string {
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)
+
+ 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")
+
+ 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)
+
+ err = pem.Encode(publicPem, publicKeyBlock)
+ CheckError(err, "error encoding public pem")
+
+ _, err = os.Stat("./pem/board/" + actor.Name + "-public.pem")
+ if os.IsNotExist(err) {
+ CheckError(err, "public pem file for actor does not exist")
+ } else {
+ StorePemToDB(db, actor)
+ }
+}
+
+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")
+
+ var result string
+ defer rows.Close()
+ rows.Next()
+ rows.Scan(&result)
+
+ if(result != "") {
+ return
+ }
+
+ publicKeyPem := actor.Id + "#main-key"
+ query = "update actor set publicKeyPem=$1 where id=$2"
+ _, err = db.Exec(query, publicKeyPem, actor.Id)
+ CheckError(err, "error updating publicKeyPem id to actor")
+
+ file := "./pem/board/" + actor.Name + "-public.pem"
+ query = "insert into publicKeyPem (id, owner, file) values($1, $2, $3)"
+ _, err = db.Exec(query, publicKeyPem, actor.Id, file)
+ CheckError(err, "error creating publicKeyPem for actor ")
+}
+
+func ActivitySign(db *sql.DB, actor Actor, signature string) string {
+ query := `select file from publicKeyPem where id=$1 `
+
+ rows, err := db.Query(query, actor.PublicKey.Id)
+
+ CheckError(err, "there was error geting actors public key id")
+
+ var file string
+ defer rows.Close()
+ rows.Next()
+ rows.Scan(&file)
+
+ file = strings.ReplaceAll(file, "public.pem", "private.pem")
+ _, err = os.Stat(file)
+ if err == nil {
+ publickey, err:= ioutil.ReadFile(file)
+ CheckError(err, "error reading file")
+
+ block, _ := pem.Decode(publickey)
+
+ pub, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
+ rng :=crand.Reader
+ hashed := sha256.New()
+ hashed.Write([]byte(signature))
+ cipher, _ := rsa.SignPKCS1v15(rng, pub, crypto.SHA256, hashed.Sum(nil))
+
+ return base64.StdEncoding.EncodeToString(cipher)
+ }
+
+ return ""
+}
+
+func ActivityVerify(actor Actor, signature string, verify string) error {
+
+ sig, _ := base64.StdEncoding.DecodeString(signature)
+
+ if actor.PublicKey.PublicKeyPem == "" {
+ actor = FingerActor(actor.Id)
+ }
+
+ block, _ := pem.Decode([]byte(actor.PublicKey.PublicKeyPem))
+ pub, _ := x509.ParsePKIXPublicKey(block.Bytes)
+ hashed := sha256.New()
+ hashed.Write([]byte(verify))
+
+ return rsa.VerifyPKCS1v15(pub.(*rsa.PublicKey), crypto.SHA256, hashed.Sum(nil), sig)
+}
+
+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 sig string
+ for i, e := range s.Headers {
+
+ var nl string
+ if i < len(s.Headers) - 1 {
+ nl = "\n"
+ }
+
+
+ if e == "(request-target)" {
+ method = strings.ToLower(r.Method)
+ path = r.URL.Path
+ sig += "(request-target): " + method + " " + path + "" + nl
+ continue
+ }
+
+ if e == "host" {
+ host = r.Host
+ sig += "host: " + host + "" + nl
+ continue
+ }
+
+ if e == "date" {
+ date = r.Header.Get("date")
+ sig += "date: " + date + "" + nl
+ continue
+ }
+
+ if e == "digest" {
+ digest = r.Header.Get("digest")
+ sig += "digest: " + digest + "" + nl
+ continue
+ }
+
+ if e == "content-length" {
+ contentLength = r.Header.Get("content-length")
+ sig += "content-length: " + contentLength + "" + nl
+ continue
+ }
+ }
+
+ if s.KeyId != actor.PublicKey.Id {
+ return false
+ }
+
+ t, _ := time.Parse(time.RFC1123, date)
+
+ if(time.Now().Sub(t).Seconds() > 30) {
+ return false
+ }
+
+ if ActivityVerify(actor, s.Signature, sig) != nil {
+ return false
+ }
+
+ return true
+}
+
+func ParseHeaderSignature(signature string) Signature {
+ var nsig Signature
+
+ keyId := regexp.MustCompile(`keyId=`)
+ headers := regexp.MustCompile(`headers=`)
+ sig := regexp.MustCompile(`signature=`)
+ algo := regexp.MustCompile(`algorithm=`)
+
+ signature = strings.ReplaceAll(signature, "\"", "")
+ parts := strings.Split(signature, ",")
+
+ for _, e := range parts {
+ if keyId.MatchString(e) {
+ nsig.KeyId = keyId.ReplaceAllString(e, "")
+ continue
+ }
+
+ if headers.MatchString(e) {
+ header := headers.ReplaceAllString(e, "")
+ nsig.Headers = strings.Split(header, " ")
+ continue
+ }
+
+ if sig.MatchString(e) {
+ nsig.Signature = sig.ReplaceAllString(e, "")
+ continue
+ }
+
+ if algo.MatchString(e) {
+ nsig.Algorithm = algo.ReplaceAllString(e, "")
+ continue
+ }
+ }
+
+ return nsig
+}
diff --git a/webfinger.go b/webfinger.go
new file mode 100644
index 0000000..004bdca
--- /dev/null
+++ b/webfinger.go
@@ -0,0 +1,12 @@
+package main
+
+type Webfinger struct {
+ Subject string `json:"subject,omitempty"`
+ Links []WebfingerLink `json:"links,omitempty"`
+}
+
+type WebfingerLink struct {
+ Rel string `json:"rel,omitempty"`
+ Type string `json:"type,omitempty"`
+ Href string `json:"href,omitempty"`
+}