aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFChannel <=>2021-01-13 17:09:43 -0800
committerFChannel <=>2021-01-13 17:09:43 -0800
commit8fb8ccafa3452d4987098ccef5c1c0bf247db555 (patch)
tree0d3acb0b988e4f1f3f97abdb9af3c464da769a55
parent2aa578319c4e01425509597772160ef403aefbfc (diff)
initial commit
-rw-r--r--.gitignore2
-rw-r--r--Database.go868
-rw-r--r--Follow.go222
-rw-r--r--OutboxPost.go545
-rw-r--r--README.md0
-rw-r--r--activityPubStruct.go191
-rw-r--r--databaseschema.psql135
-rw-r--r--main.go937
-rw-r--r--outboxGet.go120
-rw-r--r--verification.go456
10 files changed, 3476 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9b4a572
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+*~
+public/
diff --git a/Database.go b/Database.go
new file mode 100644
index 0000000..6c3f711
--- /dev/null
+++ b/Database.go
@@ -0,0 +1,868 @@
+package main
+
+import "fmt"
+import "database/sql"
+import _ "github.com/lib/pq"
+import "time"
+import "os"
+import "strings"
+
+func GetActorFromDB(db *sql.DB, id string) Actor {
+
+ query := fmt.Sprintf("SELECT type, id, name, preferedusername, inbox, outbox, following, followers, restricted, summary from actor where id='%s'", id)
+ rows, err := db.Query(query)
+
+ defer rows.Close()
+
+ var nActor Actor
+
+ if err != nil {
+ return nActor
+ }
+
+ 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)
+ CheckError(err, "error with actor from db scan ")
+ }
+
+ nActor.AtContext.Context = "https://www.w3.org/ns/activitystreams"
+
+ return nActor
+}
+
+func CreateNewBoardDB(db *sql.DB, actor Actor) Actor{
+
+ query := fmt.Sprintf("INSERT INTO actor (type, id, name, preferedusername, inbox, outbox, following, followers, summary) values ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')", actor.Type, actor.Id, actor.Name, actor.PreferredUsername, actor.Inbox, actor.Outbox, actor.Following, actor.Followers, actor.Summary)
+
+ _, err := db.Exec(query)
+
+ if err != nil {
+ fmt.Println("board exists")
+ } else {
+ fmt.Println("board added")
+ for _, e := range actor.AuthRequirement {
+ query = fmt.Sprintf("INSERT INTO actorauth (type, board) values ('%s', '%s')", e, actor.Name)
+ _, err := db.Exec(query)
+ CheckError(err, "auth exists")
+ }
+
+ var verify Verify
+
+ verify.Identifier = actor.Id
+ verify.Code = CreateKey(50)
+ verify.Type = "admin"
+
+ CreateVerification(db, verify)
+
+ verify.Identifier = actor.Id
+ verify.Code = CreateKey(50)
+ verify.Type = "janitor"
+
+ CreateVerification(db, verify)
+
+ var nverify Verify
+ nverify.Board = actor.Id
+ nverify.Identifier = "admin"
+ nverify.Type = "admin"
+ CreateBoardMod(db, nverify)
+
+ nverify.Board = actor.Id
+ nverify.Identifier = "janitor"
+ nverify.Type = "janitor"
+ CreateBoardMod(db, nverify)
+
+ if actor.Name != "main" {
+ var nActivity Activity
+ var nActor Actor
+ var nObject ObjectBase
+
+ nActor.Id = Domain
+ nObject.Id = actor.Id
+
+ nActivity.Actor = &nActor
+ nActivity.Object = &nObject
+
+ SetActorFollowDB(db, nActivity, Domain)
+ }
+ }
+
+ return actor
+}
+
+func GetBoards(db *sql.DB) []Actor {
+
+ var board []Actor
+
+ query := fmt.Sprintf("select type, id, name, preferedusername, inbox, outbox, following, followers FROM actor")
+
+ rows, err := db.Query(query)
+
+ defer rows.Close()
+
+ if err !=nil{
+ panic(err)
+ }
+
+ defer rows.Close()
+
+ for rows.Next(){
+ var actor = new(Actor)
+
+ err = rows.Scan(&actor.Type, &actor.Id, &actor.Name, &actor.PreferredUsername, &actor.Inbox, &actor.Outbox, &actor.Following, &actor.Followers)
+
+ if err !=nil{
+ panic(err)
+ }
+
+ board = append(board, *actor)
+ }
+
+ return board
+}
+
+func writeObjectToDB(db *sql.DB, obj ObjectBase) ObjectBase {
+ obj.Id = fmt.Sprintf("%s/%s", obj.Actor.Id, CreateUniqueID(db, obj.Actor.Id))
+ if len(obj.Attachment) > 0 {
+ for i, _ := range obj.Attachment {
+ obj.Attachment[i].Id = fmt.Sprintf("%s/%s", obj.Actor.Id, CreateUniqueID(db, obj.Actor.Id))
+ obj.Attachment[i].Published = time.Now().Format(time.RFC3339)
+ obj.Attachment[i].Updated = time.Now().Format(time.RFC3339)
+ writeAttachmentToDB(db, obj.Attachment[i])
+ writeActivitytoDBWithAttachment(db, obj, obj.Attachment[i])
+ }
+ } else {
+ writeActivitytoDB(db, obj)
+ }
+
+ writeObjectReplyToDB(db, obj)
+ WriteWalletToDB(db, obj)
+
+ return obj
+}
+
+func WriteObjectUpdatesToDB(db *sql.DB, obj ObjectBase) {
+ query := fmt.Sprintf("update activitystream set updated='%s' where id='%s'", time.Now().Format(time.RFC3339), obj.Id)
+ _, e := db.Exec(query)
+
+ if e != nil{
+ fmt.Println("error inserting updating inreplyto")
+ panic(e)
+ }
+}
+
+func WriteObjectReplyToLocalDB(db *sql.DB, id string, replyto string) {
+ query := fmt.Sprintf("insert into replies (id, inreplyto) values ('%s', '%s')", id, replyto)
+
+ _, err := db.Exec(query)
+
+ CheckError(err, "Could not insert local reply query")
+
+ query = fmt.Sprintf("select inreplyto from replies where id='%s'", replyto)
+
+ rows, _ := db.Query(query)
+
+ CheckError(err, "Could not query select inreplyto")
+
+ defer rows.Close()
+
+ for rows.Next() {
+ var val string
+ rows.Scan(&val)
+ if val == "" {
+ updated := time.Now().Format(time.RFC3339)
+ query := fmt.Sprintf("update activitystream set updated='%s' where id='%s'", updated, replyto)
+
+ _, err := db.Exec(query)
+
+ CheckError(err, "error with updating replyto updated at date")
+ }
+ }
+}
+
+func writeObjectReplyToDB(db *sql.DB, obj ObjectBase) {
+ for i, e := range obj.InReplyTo {
+ if(i == 0 || IsReplyInThread(db, obj.InReplyTo[0].Id, e.Id)){
+ query := fmt.Sprintf("insert into replies (id, inreplyto) values ('%s', '%s')", obj.Id, e.Id)
+ _, err := db.Exec(query)
+
+ if err != nil{
+ fmt.Println("error inserting replies")
+ panic(err)
+ }
+ }
+
+ update := true
+ for _, e := range obj.Option {
+ if e == "sage" || e == "nokosage" {
+ update = false
+ break
+ }
+ }
+
+ if update {
+ WriteObjectUpdatesToDB(db, e)
+ }
+ }
+}
+
+
+func WriteWalletToDB(db *sql.DB, obj ObjectBase) {
+ for _, e := range obj.Option {
+ if e == "wallet" {
+ for _, e := range obj.Wallet {
+ query := fmt.Sprintf("insert into wallet (id, type, address) values ('%s', '%s', '%s')", obj.Id ,e.Type, e.Address)
+ _, err := db.Exec(query)
+
+ CheckError(err, "error with write wallet query")
+ }
+ return
+ }
+ }
+}
+
+func writeActivitytoDB(db *sql.DB, obj ObjectBase) {
+
+ obj.Name = EscapeString(obj.Name)
+ obj.Content = EscapeString(obj.Content)
+ obj.AttributedTo = EscapeString(obj.AttributedTo)
+
+ query := fmt.Sprintf("insert into activitystream (id, type, name, content, published, updated, attributedto, actor) values ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')", obj.Id ,obj.Type, obj.Name, obj.Content, obj.Published, obj.Updated, obj.AttributedTo, obj.Actor.Id)
+
+ _, e := db.Exec(query)
+
+ if e != nil{
+ fmt.Println("error inserting new activity")
+ panic(e)
+ }
+}
+
+func writeActivitytoDBWithAttachment(db *sql.DB, obj ObjectBase, attachment ObjectBase) {
+
+ obj.Name = EscapeString(obj.Name)
+ obj.Content = EscapeString(obj.Content)
+ obj.AttributedTo = EscapeString(obj.AttributedTo)
+
+ query := fmt.Sprintf("insert into activitystream (id, type, name, content, attachment, published, updated, attributedto, actor) values ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')", obj.Id ,obj.Type, obj.Name, obj.Content, attachment.Id, obj.Published, obj.Updated, obj.AttributedTo, obj.Actor.Id)
+
+ _, e := db.Exec(query)
+
+ if e != nil{
+ fmt.Println("error inserting new activity with attachment")
+ panic(e)
+ }
+}
+
+func writeAttachmentToDB(db *sql.DB, obj ObjectBase) {
+ query := fmt.Sprintf("insert into activitystream (id, type, name, href, published, updated, attributedTo, mediatype, size) values ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d');", obj.Id ,obj.Type, obj.Name, obj.Href, obj.Published, obj.Updated, obj.AttributedTo, obj.MediaType, obj.Size)
+
+ _, e := db.Exec(query)
+
+ if e != nil{
+ fmt.Println("error inserting new attachment")
+ panic(e)
+ }
+}
+
+func GetActivityFromDB(db *sql.DB, id string) Collection {
+ var nColl Collection
+ var result []ObjectBase
+
+ query := fmt.Sprintf("SELECT actor, id, name, content, type, published, updated, attributedto, attachment, actor FROM activitystream WHERE id='%s' ORDER BY updated asc;", id)
+
+ rows, err := db.Query(query)
+
+ defer rows.Close()
+
+ CheckError(err, "error query object from db")
+
+ defer rows.Close()
+
+ for rows.Next(){
+ var post ObjectBase
+ var actor Actor
+ var attachID string
+
+ err = rows.Scan(&nColl.Actor, &post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.Updated, &post.AttributedTo, &attachID, &actor.Id)
+
+ CheckError(err, "error scan object into post struct")
+
+ post.Actor = &actor
+
+ post.Replies = GetObjectRepliesDB(db, post)
+
+ post.Replies.TotalItems, post.Replies.TotalImgs = GetObjectRepliesDBCount(db, post)
+
+ post.Attachment = GetObjectAttachment(db, attachID)
+
+ result = append(result, post)
+ }
+
+ nColl.OrderedItems = result
+
+ return nColl
+}
+
+func GetObjectFromDB(db *sql.DB, actor Actor) Collection {
+ var nColl Collection
+ var result []ObjectBase
+
+ query := fmt.Sprintf("SELECT id, name, content, type, published, updated, attributedto, attachment, actor FROM activitystream WHERE actor='%s' AND id IN (SELECT id FROM replies WHERE inreplyto='') AND type='Note' ORDER BY updated asc;", actor.Id)
+
+ rows, err := db.Query(query)
+
+ defer rows.Close()
+
+ CheckError(err, "error query object from db")
+
+ defer rows.Close()
+
+ for rows.Next(){
+ var post ObjectBase
+ var actor Actor
+ var attachID string
+
+ err = rows.Scan(&post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.Updated, &post.AttributedTo, &attachID, &actor.Id)
+
+ CheckError(err, "error scan object into post struct")
+
+ post.Actor = &actor
+
+ post.Replies = GetObjectRepliesDB(db, post)
+
+ post.Replies.TotalItems, post.Replies.TotalImgs = GetObjectRepliesDBCount(db, post)
+
+ post.Attachment = GetObjectAttachment(db, attachID)
+
+ result = append(result, post)
+ }
+
+ nColl.OrderedItems = result
+
+ return nColl
+}
+
+func GetInReplyToDB(db *sql.DB, parent ObjectBase) []ObjectBase {
+ var result []ObjectBase
+
+ query := fmt.Sprintf("SELECT inreplyto FROM replies WHERE id ='%s'", parent.Id)
+
+ rows, err := db.Query(query)
+
+ defer rows.Close()
+
+ if err !=nil{
+ fmt.Println("error with inreplyto db query")
+ panic(err)
+ }
+
+ defer rows.Close()
+
+ for rows.Next() {
+ var post ObjectBase
+
+ rows.Scan(&post.Id)
+
+ result = append(result, post)
+ }
+
+ return result
+}
+
+
+func GetObjectRepliesDB(db *sql.DB, parent ObjectBase) *CollectionBase {
+
+ var nColl CollectionBase
+ var result []ObjectBase
+
+ query := fmt.Sprintf("SELECT id, name, content, type, published, attributedto, attachment, actor FROM activitystream WHERE id IN (SELECT id FROM replies WHERE inreplyto='%s') AND type='Note' ORDER BY published asc;", parent.Id)
+
+ rows, err := db.Query(query)
+
+ defer rows.Close()
+
+ if err !=nil{
+ fmt.Println("error with replies db query")
+ panic(err)
+ }
+
+ defer rows.Close()
+
+ for rows.Next() {
+ var post ObjectBase
+ var actor Actor
+ var attachID string
+
+ post.InReplyTo = append(post.InReplyTo, parent)
+
+ err = rows.Scan(&post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.AttributedTo, &attachID, &actor.Id)
+
+
+ CheckError(err, "error with replies db scan")
+
+ post.Actor = &actor
+
+ post.Replies = GetObjectRepliesRepliesDB(db, post)
+
+ post.Replies.TotalItems, post.Replies.TotalImgs = GetObjectRepliesDBCount(db, post)
+
+ post.Attachment = GetObjectAttachment(db, attachID)
+
+ result = append(result, post)
+ }
+
+ nColl.OrderedItems = result
+
+ remoteCollection := GetObjectRepliesRemote(db, parent)
+
+ for _, e := range remoteCollection.OrderedItems {
+
+ nColl.OrderedItems = append(nColl.OrderedItems, e)
+
+ }
+
+ return &nColl
+}
+
+func GetObjectRepliesRemote(db *sql.DB, parent ObjectBase) CollectionBase {
+ var nColl CollectionBase
+ var result []ObjectBase
+ query := fmt.Sprintf("select id from replies where id not in (select id from activitystream) and inreplyto='%s'", parent.Id)
+
+ rows, err := db.Query(query)
+
+ CheckError(err, "could not get remote id query")
+
+ for rows.Next() {
+ var id string
+ rows.Scan(&id)
+
+ coll := GetCollectionFromID(id)
+
+ for _, e := range coll.OrderedItems {
+ result = append(result, e)
+ }
+ }
+
+ nColl.OrderedItems = result
+
+ return nColl
+}
+
+func GetObjectRepliesRepliesDB(db *sql.DB, parent ObjectBase) *CollectionBase {
+
+ var nColl CollectionBase
+ var result []ObjectBase
+
+ query := fmt.Sprintf("SELECT id, name, content, type, published, attributedto, attachment, actor FROM activitystream WHERE id IN (SELECT id FROM replies WHERE inreplyto='%s') AND type='Note' ORDER BY published asc;", parent.Id)
+
+ rows, err := db.Query(query)
+
+ defer rows.Close()
+
+ if err !=nil{
+ fmt.Println("error with replies db query")
+ panic(err)
+ }
+
+ defer rows.Close()
+
+ for rows.Next() {
+ var post ObjectBase
+ var actor Actor
+ var attachID string
+
+ post.InReplyTo = append(post.InReplyTo, parent)
+
+ err = rows.Scan(&post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.AttributedTo, &attachID, &actor.Id)
+
+
+ CheckError(err, "error with replies replies db scan")
+
+ post.Actor = &actor
+
+ post.Attachment = GetObjectAttachment(db, attachID)
+
+ result = append(result, post)
+ }
+
+ remoteCollection := GetObjectRepliesRemote(db, parent)
+
+ for _, e := range remoteCollection.OrderedItems {
+
+ nColl.OrderedItems = append(nColl.OrderedItems, e)
+
+ }
+
+ nColl.OrderedItems = result
+
+ return &nColl
+}
+
+func GetObjectRepliesDBCount(db *sql.DB, parent ObjectBase) (int, int) {
+
+ var countId int
+ var countImg int
+
+ query := fmt.Sprintf("SELECT COUNT(id) FROM replies WHERE inreplyto ='%s' and id in (select id from activitystream where type='Note');", parent.Id)
+
+ rows, err := db.Query(query)
+
+ defer rows.Close()
+
+ if err !=nil{
+ fmt.Println("error with replies count db query")
+ }
+
+ defer rows.Close()
+
+ for rows.Next() {
+ err = rows.Scan(&countId)
+
+ if err !=nil{
+ fmt.Println("error with replies count db scan")
+ }
+ }
+
+ query = fmt.Sprintf("SELECT COUNT(attachment) FROM activitystream WHERE id IN (SELECT id FROM replies WHERE inreplyto ='%s') AND attachment != '';", parent.Id)
+
+ rows, err = db.Query(query)
+
+ defer rows.Close()
+
+ if err !=nil{
+ fmt.Println("error with replies count db query")
+ }
+
+ defer rows.Close()
+
+ for rows.Next() {
+ err = rows.Scan(&countImg)
+
+ if err !=nil{
+ fmt.Println("error with replies count db scan")
+ }
+ }
+
+ return countId, countImg
+}
+
+func GetObjectAttachment(db *sql.DB, id string) []ObjectBase {
+
+ var attachments []ObjectBase
+
+ query := fmt.Sprintf("SELECT id, type, name, href, mediatype, size, published FROM activitystream WHERE id='%s'", id)
+
+ rows, err := db.Query(query)
+
+ defer rows.Close()
+
+ defer rows.Close()
+ for rows.Next() {
+ var attachment = new(ObjectBase)
+
+ err = rows.Scan(&attachment.Id, &attachment.Type, &attachment.Name, &attachment.Href, &attachment.MediaType, &attachment.Size, &attachment.Published)
+ if err !=nil{
+ fmt.Println("error with attachment db query")
+ panic(err)
+ }
+
+ attachments = append(attachments, *attachment)
+ }
+
+ return attachments
+}
+
+func GetObjectPostsTotalDB(db *sql.DB, actor Actor) int{
+
+ count := 0
+ query := fmt.Sprintf("SELECT COUNT(id) FROM activitystream WHERE actor='%s' AND id IN (SELECT id FROM replies WHERE inreplyto='' AND type='Note');", actor.Id)
+
+ rows, err := db.Query(query)
+
+ defer rows.Close()
+
+ if err !=nil{
+ fmt.Println("error with posts total db query")
+ }
+
+ defer rows.Close()
+ for rows.Next() {
+ err = rows.Scan(&count)
+
+ CheckError(err, "error with total post db scan")
+ }
+
+ return count
+}
+
+func GetObjectImgsTotalDB(db *sql.DB, actor Actor) int{
+
+ count := 0
+ query := fmt.Sprintf("SELECT COUNT(attachment) FROM activitystream WHERE actor='%s' AND id IN (SELECT id FROM replies WHERE inreplyto='' AND type='Note' );", actor.Id)
+
+ rows, err := db.Query(query)
+
+ if err !=nil{
+ fmt.Println("error with posts total db query")
+ }
+
+ defer rows.Close()
+ for rows.Next() {
+ err = rows.Scan(&count)
+
+ CheckError(err, "error with total post db scan")
+ }
+
+ return count
+}
+
+
+func DeleteAttachmentFromFile(db *sql.DB, id string) {
+
+ var query = fmt.Sprintf("select href, type from activitystream where id in (select attachment from activitystream where id='%s')", id)
+
+ rows, err := db.Query(query)
+
+ if err != nil {
+ fmt.Println("error query delete attachment")
+ }
+
+ defer rows.Close()
+
+ for rows.Next() {
+ var href string
+ var _type string
+ err := rows.Scan(&href, &_type)
+ href = strings.Replace(href, Domain + "/", "", 1)
+
+ if err != nil {
+ fmt.Println("error scanning delete attachment")
+ }
+
+ if _type != "Tombstone" {
+ _, err = os.Stat(href)
+ CheckError(err, "err removing file from system")
+ if err == nil {
+ os.Remove(href)
+ }
+ }
+
+ }
+
+ DeleteAttachmentFromDB(db, id)
+}
+
+
+func DeleteAttachmentRepliesFromDB(db *sql.DB, id string) {
+ var query = fmt.Sprintf("select id from activitystream where id (select id from replies where inreplyto='%s');", id)
+
+ rows, err := db.Query(query)
+
+ defer rows.Close()
+
+ if err != nil {
+ fmt.Println("error query delete attachment replies")
+ }
+
+ defer rows.Close()
+
+ for rows.Next() {
+ var attachment string
+
+ err := rows.Scan(&attachment)
+
+ if err != nil {
+ fmt.Println("error scanning delete attachment")
+ }
+
+ DeleteAttachmentFromFile(db, attachment)
+ }
+}
+
+
+func DeleteAttachmentFromDB(db *sql.DB, id string) {
+ datetime := time.Now().Format(time.RFC3339)
+
+ var query = fmt.Sprintf("update activitystream set type='Tombstone', mediatype='image/png', href='%s', name='', content='', attributedto='deleted', updated='%s', deleted='%s' where id in (select attachment from activitystream where id='%s');", Domain + "/public/removed.png", datetime, datetime, id)
+
+ _, err := db.Exec(query)
+
+ CheckError(err, "error with delete attachment")
+}
+
+
+func DeleteObjectFromDB(db *sql.DB, id string) {
+ datetime := time.Now().Format(time.RFC3339)
+ var query = fmt.Sprintf("update activitystream set type='Tombstone', name='', content='', attributedto='deleted', updated='%s', deleted='%s' where id='%s';", datetime, datetime, id)
+
+ _, err := db.Exec(query)
+
+ CheckError(err, "error with delete object")
+}
+
+func DeleteObjectRepliesFromDB(db *sql.DB, id string) {
+ datetime := time.Now().Format(time.RFC3339)
+ var query = fmt.Sprintf("update activitystream set type='Tombstone', name='', content='', attributedto='deleted' updated='%s', deleted='%s' where id in (select id from replies where inreplyto='%s');", datetime, datetime, id)
+
+ _, err := db.Exec(query)
+ CheckError(err, "error with delete object replies")
+}
+
+func DeleteObject(db *sql.DB, id string) {
+
+ if(!IsIDLocal(db, id)) {
+ return
+ }
+
+ DeleteObjectFromDB(db, id)
+ DeleteReportActivity(db, id)
+ DeleteAttachmentFromFile(db, id)
+}
+
+func DeleteObjectAndReplies(db *sql.DB, id string) {
+
+ if(!IsIDLocal(db, id)) {
+ return
+ }
+
+ DeleteObjectFromDB(db, id)
+ DeleteReportActivity(db, id)
+ DeleteAttachmentFromFile(db, id)
+ DeleteObjectRepliesFromDB(db, id)
+ DeleteAttachmentRepliesFromDB(db, id)
+}
+
+func GetRandomCaptcha(db *sql.DB) string{
+ query := fmt.Sprintf("select identifier from verification where type='captcha' order by random() limit 1")
+ rows, err := db.Query(query)
+
+ CheckError(err, "could not get captcha")
+
+ var verify string
+
+ rows.Next()
+ err = rows.Scan(&verify)
+
+ rows.Close()
+
+ CheckError(err, "Could not get verify captcha")
+
+ return verify
+}
+
+func GetCaptchaTotal(db *sql.DB) int{
+ query := fmt.Sprintf("select count(*) from verification where type='captcha'")
+ rows, err := db.Query(query)
+
+ defer rows.Close()
+
+ CheckError(err, "could not get query captcha total")
+
+ var count int
+ for rows.Next(){
+ if err := rows.Scan(&count); err != nil{
+ CheckError(err, "could not get captcha total")
+ }
+ }
+
+ return count
+}
+
+func GetCaptchaCodeDB(db *sql.DB, verify string) string {
+
+ query := fmt.Sprintf("select code from verification where identifier='%s' limit 1", verify)
+ rows, err := db.Query(query)
+
+ defer rows.Close()
+
+ CheckError(err, "could not get captcha verifciation")
+
+ var code string
+
+ rows.Next()
+ err = rows.Scan(&code)
+
+ CheckError(err, "Could not get verification captcha")
+
+ return code
+}
+
+func GetActorAuth(db *sql.DB, actor string) []string {
+ query := fmt.Sprintf("select type from actorauth where board='%s'", actor)
+
+ rows, err := db.Query(query)
+
+ defer rows.Close()
+
+ CheckError(err, "could not get actor auth")
+
+ var auth []string
+
+ for rows.Next() {
+ var e string
+ err = rows.Scan(&e)
+
+ auth = append(auth, e)
+
+ CheckError(err, "could not get actor auth row scan")
+ }
+
+ return auth
+}
+
+func DeleteCaptchaCodeDB(db *sql.DB, verify string) {
+ query := fmt.Sprintf("delete from verification where identifier='%s'", verify)
+
+ _, err := db.Exec(query);
+
+ CheckError(err, "could not delete captcah code db")
+
+ os.Remove("./" + verify)
+}
+
+func EscapeString(text string) string {
+ text = strings.Replace(text, "'", "''", -1)
+ text = strings.Replace(text, "&", "&amp;", -1)
+ text = strings.Replace(text, "<", "&lt;", -1)
+ return text
+}
+
+func GetActorReportedTotal(db *sql.DB, id string) int {
+ query := fmt.Sprintf("select count(id) from reported where board='%s'", id)
+
+ rows, err := db.Query(query)
+
+ CheckError(err, "error getting actor reported total query")
+
+ defer rows.Close()
+
+ var count int
+ for rows.Next() {
+ rows.Scan(&count)
+ }
+
+ return count
+}
+
+func GetActorReportedDB(db *sql.DB, id string) []ObjectBase {
+ var nObj []ObjectBase
+
+ query := fmt.Sprintf("select id, count from reported where board='%s'", id)
+
+ rows, err := db.Query(query)
+
+ CheckError(err, "error getting actor reported query")
+
+ defer rows.Close()
+
+ for rows.Next() {
+ var obj ObjectBase
+
+ rows.Scan(&obj.Id, &obj.Size)
+
+ nObj = append(nObj, obj)
+ }
+
+ return nObj
+}
diff --git a/Follow.go b/Follow.go
new file mode 100644
index 0000000..475417b
--- /dev/null
+++ b/Follow.go
@@ -0,0 +1,222 @@
+package main
+
+import "fmt"
+import "net/http"
+import "database/sql"
+import _ "github.com/lib/pq"
+import "encoding/json"
+
+func GetActorFollowing(w http.ResponseWriter, db *sql.DB, id string) {
+ var following Collection
+
+ following.AtContext.Context = "https://www.w3.org/ns/activitystreams"
+ following.Type = "Collection"
+ following.TotalItems, _ = GetActorFollowTotal(db, id)
+ following.Items, _ = GetActorFollowDB(db, id)
+
+ enc, _ := json.MarshalIndent(following, "", "\t")
+ w.Header().Set("Content-Type", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"")
+ w.Write(enc)
+}
+
+func GetActorFollowers(w http.ResponseWriter, db *sql.DB, id string) {
+ var following Collection
+
+ following.AtContext.Context = "https://www.w3.org/ns/activitystreams"
+ following.Type = "Collection"
+ _, following.TotalItems = GetActorFollowTotal(db, id)
+ _, following.Items = GetActorFollowDB(db, id)
+
+ enc, _ := json.MarshalIndent(following, "", "\t")
+ w.Header().Set("Content-Type", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"")
+ w.Write(enc)
+}
+
+func SetActorFollowDB(db *sql.DB, activity Activity, actor string) Activity {
+ var query string
+ alreadyFollow := false
+ following, follower := GetActorFollowDB(db, actor)
+
+ if activity.Actor.Id == actor {
+ for _, e := range following {
+ if e.Id == activity.Object.Id {
+ alreadyFollow = true
+ }
+ }
+ if alreadyFollow {
+ query = fmt.Sprintf("delete from following where id='%s' and following='%s'", activity.Actor.Id, activity.Object.Id)
+ activity.Summary = activity.Actor.Id + " Unfollow " + activity.Object.Id
+ } else {
+ query = fmt.Sprintf("insert into following (id, following) values ('%s', '%s')", activity.Actor.Id, activity.Object.Id)
+ activity.Summary = activity.Actor.Id + " Follow " + activity.Object.Id
+ }
+ } else {
+ for _, e := range follower {
+ if e.Id == activity.Actor.Id {
+ alreadyFollow = true
+ }
+ }
+ if alreadyFollow {
+ query = fmt.Sprintf("delete from follower where id='%s' and follower='%s'", activity.Object.Id, activity.Actor.Id)
+ activity.Summary = activity.Actor.Id + " Unfollow " + activity.Object.Id
+ } else {
+ query = fmt.Sprintf("insert into follower (id, follower) values ('%s', '%s')", activity.Object.Id, activity.Actor.Id)
+ activity.Summary = activity.Actor.Id + " Follow " + activity.Object.Id
+ }
+ }
+
+ _, err := db.Exec(query)
+
+ CheckError(err, "error with follow db insert/delete")
+
+ return activity
+}
+
+func GetActorFollowDB(db *sql.DB, id string) ([]ObjectBase, []ObjectBase) {
+ var followingCollection []ObjectBase
+ var followerCollection []ObjectBase
+
+ query := fmt.Sprintf("SELECT following FROM following WHERE id='%s'", id)
+
+ rows, err := db.Query(query)
+
+ CheckError(err, "error with following db query")
+
+ defer rows.Close()
+
+ for rows.Next() {
+ var obj ObjectBase
+
+ err := rows.Scan(&obj.Id)
+
+ CheckError(err, "error with following db scan")
+
+ followingCollection = append(followingCollection, obj)
+ }
+
+ query = fmt.Sprintf("SELECT follower FROM follower WHERE id='%s'", id)
+
+ rows, err = db.Query(query)
+
+ CheckError(err, "error with followers db query")
+
+ defer rows.Close()
+
+ for rows.Next() {
+ var obj ObjectBase
+
+ err := rows.Scan(&obj.Id)
+
+ CheckError(err, "error with followers db scan")
+
+ followerCollection = append(followerCollection, obj)
+ }
+
+ return followingCollection, followerCollection
+}
+
+func GetActorFollowTotal(db *sql.DB, id string) (int, int) {
+ var following int
+ var followers int
+
+ query := fmt.Sprintf("SELECT COUNT(following) FROM following WHERE id='%s'", id)
+
+ rows, err := db.Query(query)
+
+ CheckError(err, "error with following total db query")
+
+ defer rows.Close()
+
+ for rows.Next() {
+ err := rows.Scan(&following)
+
+ CheckError(err, "error with following total db scan")
+ }
+
+ query = fmt.Sprintf("SELECT COUNT(follower) FROM follower WHERE id='%s'", id)
+
+ rows, err = db.Query(query)
+
+ CheckError(err, "error with followers total db query")
+
+ defer rows.Close()
+
+ for rows.Next() {
+ err := rows.Scan(&followers)
+
+ CheckError(err, "error with followers total db scan")
+ }
+
+ return following, followers
+}
+
+func AcceptFollow(activity Activity, actor Actor) Activity {
+ var accept Activity
+ var obj ObjectBase
+
+ obj.Type = activity.Type
+ obj.Actor = activity.Actor
+
+ var nobj NestedObjectBase
+ obj.Object = &nobj
+ obj.Object.Id = activity.Object.Id
+
+ accept.AtContext.Context = activity.AtContext.Context
+ accept.Type = "Accept"
+
+ var nactor Actor
+ accept.Actor = &nactor
+ accept.Actor.Id = actor.Id
+ accept.Object = &obj
+ accept.To = append(accept.To, activity.Object.Id)
+
+ return accept
+}
+
+func RejectFollow(activity Activity, actor Actor) Activity {
+ var accept Activity
+ var obj ObjectBase
+
+ obj.Type = activity.Type
+ obj.Actor = activity.Actor
+ obj.Object = new(NestedObjectBase)
+ obj.Object.Id = activity.Object.Id
+
+ accept.AtContext.Context = activity.AtContext.Context
+ accept.Type = "Reject"
+ accept.Actor = &actor
+ accept.Object = &obj
+
+ return accept
+}
+
+func SetActorFollowingDB(db *sql.DB, activity Activity) Activity{
+ var query string
+ alreadyFollow := false
+ _, follower := GetActorFollowDB(db, activity.Object.Id)
+
+ for _, e := range follower {
+ if e.Id == activity.Object.Id {
+ alreadyFollow = true
+ }
+ }
+
+ if alreadyFollow {
+ query = fmt.Sprintf("delete from follower where id='%s' and follower='%s'", activity.Object.Id, activity.Actor.Id)
+ activity.Summary = activity.Actor.Id + " Unfollow " + activity.Object.Id
+ } else {
+ query = fmt.Sprintf("insert into follower (id, follower) values ('%s', '%s')", activity.Object.Id, activity.Actor.Id)
+ activity.Summary = activity.Actor.Id + " Follow " + activity.Object.Id
+ }
+
+ _, err := db.Exec(query)
+
+ if err != nil {
+ CheckError(err, "error with follow db insert/delete")
+ activity.Type = "Reject"
+ return activity
+ }
+
+ activity.Type = "Accept"
+ return activity
+}
diff --git a/OutboxPost.go b/OutboxPost.go
new file mode 100644
index 0000000..89173f9
--- /dev/null
+++ b/OutboxPost.go
@@ -0,0 +1,545 @@
+package main
+
+import "fmt"
+import "net/http"
+import "database/sql"
+import _ "github.com/lib/pq"
+import "encoding/json"
+import "reflect"
+import "io/ioutil"
+import "os"
+import "regexp"
+import "strings"
+
+func ParseOutboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) {
+
+ var activity Activity
+ var object ObjectBase
+
+ actor := GetActorFromPath(db, r.URL.Path, "/")
+ contentType := GetContentType(r.Header.Get("content-type"))
+
+ defer r.Body.Close()
+ if contentType == "multipart/form-data" || contentType == "application/x-www-form-urlencoded" {
+ r.ParseMultipartForm(5 << 20)
+ if(BoardHasAuthType(db, actor.Name, "captcha") && CheckCaptcha(db, r.FormValue("captcha"))) {
+ f, header, _ := r.FormFile("file")
+ if(header != nil) {
+ if(header.Size > (5 << 20)){
+ w.WriteHeader(http.StatusRequestEntityTooLarge)
+ w.Write([]byte("5MB max file size"))
+ return
+ }
+
+ contentType, _ := GetFileContentType(f)
+
+ if(!SupportedMIMEType(contentType)) {
+ w.WriteHeader(http.StatusNotAcceptable)
+ w.Write([]byte("file type not supported"))
+ return
+ }
+
+ }
+
+ var nObj = CreateObject("Note")
+ nObj = ObjectFromForm(r, db, nObj)
+
+ var act Actor
+ nObj.Actor = &act
+ nObj.Actor.Id = Domain + "/" + actor.Name
+
+ delete := regexp.MustCompile("delete:.+")
+ for _, e := range nObj.Option {
+ if delete.MatchString(e) {
+ verification := strings.Replace(e, "delete:", "", 1)
+ if HasAuth(db, verification, Domain + "/" + actor.Name) {
+ for _, e := range nObj.InReplyTo {
+ if IsObjectLocal(db, e.Id) && e.Id != nObj.InReplyTo[len(nObj.InReplyTo) - 1].Id {
+ DeleteObject(db, e.Id)
+ nObj.Type = "Delete"
+ }
+ }
+ }
+ }
+ }
+
+ if nObj.Type != "Delete" {
+ nObj = writeObjectToDB(db, nObj)
+ activity := CreateActivity("Create", nObj)
+ MakeActivityRequest(activity)
+ }
+
+ var id string
+ re := regexp.MustCompile("\\w+$")
+ op := len(nObj.InReplyTo) - 1
+ if op >= 0 {
+ if nObj.InReplyTo[op].Id == "" {
+ id = re.FindString(nObj.Id)
+ } else {
+ id = re.FindString(nObj.InReplyTo[op].Id)
+ }
+ }
+
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(id))
+ }
+
+
+
+ } else {
+ activity = GetActivityFromJson(r, db)
+ if IsActivityLocal(db, activity) {
+ switch activity.Type {
+ case "Create":
+ if(true) {
+ object = GetObjectFromActivity(activity)
+ writeObjectToDB(db, object)
+ w.WriteHeader(http.StatusCreated)
+ w.Header().Set("Location", object.Id)
+ } else {
+ w.WriteHeader(http.StatusUnauthorized)
+ w.Write([]byte(""))
+ }
+
+ case "Follow":
+ var validActor bool
+ var validLocalActor bool
+
+ _, validActor = IsValidActor(activity.Object.Id)
+ validLocalActor = (activity.Actor.Id == actor.Id) || (activity.Object.Id == actor.Id)
+ verification := GetVerificationByCode(db, activity.Auth)
+
+ var rActivity Activity
+
+ if validActor && validLocalActor && verification.Board == activity.Actor.Id {
+ rActivity = AcceptFollow(activity, actor)
+ } else {
+ rActivity = RejectFollow(activity, actor)
+ rActivity.Summary = "No valid actor or Actor is not located here"
+ }
+
+ if rActivity.Type == "Accept" {
+ rActivity.Summary = SetActorFollowDB(db, activity, actor.Id).Summary
+ }
+
+ enc, _ := json.MarshalIndent(rActivity, "", "\t")
+
+ if rActivity.Type == "Reject" {
+ w.WriteHeader(http.StatusBadRequest)
+ }
+
+ w.Header().Set("Content-Type", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"")
+ w.Write(enc)
+
+ case "Delete":
+ fmt.Println("This is a delete")
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte("could not process activity"))
+
+ case "Note":
+ fmt.Println("This is a note")
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte("could not process activity"))
+
+ case "New":
+ fmt.Println("Added new Board")
+ name := activity.Object.Actor.Name
+ prefname := activity.Object.Actor.PreferredUsername
+ summary := activity.Object.Actor.Summary
+ restricted := activity.Object.Actor.Restricted
+
+ actor := CreateNewBoardDB(db, *CreateNewActor(name, prefname, summary, authReq, restricted))
+
+ if actor.Id != "" {
+ j, _ := json.Marshal(&actor)
+ w.Write([]byte(j))
+ return
+ }
+
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+
+ default:
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte("could not process activity"))
+ }
+ } else {
+ fmt.Println("is NOT activity")
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte("could not process activity"))
+ }
+ }
+}
+
+func ObjectFromJson(r *http.Request, obj ObjectBase) ObjectBase {
+ body, _ := ioutil.ReadAll(r.Body)
+
+ var respActivity ActivityRaw
+
+ err := json.Unmarshal(body, &respActivity)
+
+ CheckError(err, "error with object from json")
+
+ if HasContextFromJson(respActivity.AtContextRaw.Context) {
+ var jObj ObjectBase
+ jObj = GetObjectFromJson(respActivity.ObjectRaw)
+ jObj.To = GetToFromJson(respActivity.ToRaw)
+ jObj.Cc = GetToFromJson(respActivity.CcRaw)
+ }
+
+ return obj
+}
+
+func GetObjectFromJson(obj []byte) ObjectBase {
+ var generic interface{}
+
+ err := json.Unmarshal(obj, &generic)
+
+ CheckError(err, "error with getting obj from json")
+
+ t := reflect.TypeOf(generic)
+
+ var nObj ObjectBase
+ if t != nil {
+ switch t.String() {
+ case "[]interface {}":
+ var lObj ObjectBase
+ var arrContext ObjectArray
+ err = json.Unmarshal(obj, &arrContext.Object)
+ CheckError(err, "error with []interface{} oject from json")
+ if len(arrContext.Object) > 0 {
+ lObj = arrContext.Object[0]
+ }
+ nObj = lObj
+ break
+
+ case "map[string]interface {}":
+ var arrContext Object
+ err = json.Unmarshal(obj, &arrContext.Object)
+ CheckError(err, "error with object from json")
+ nObj = *arrContext.Object
+ break
+
+ case "string":
+ var lObj ObjectBase
+ var arrContext ObjectString
+ err = json.Unmarshal(obj, &arrContext.Object)
+ CheckError(err, "error with string object from json")
+ lObj.Id = arrContext.Object
+ nObj = lObj
+ break
+ }
+ }
+
+ return nObj
+}
+
+func GetActorFromJson(actor []byte) Actor{
+ var generic interface{}
+ var nActor Actor
+ err := json.Unmarshal(actor, &generic)
+
+ if err != nil {
+ return nActor
+ }
+
+ t := reflect.TypeOf(generic)
+ if t != nil {
+ switch t.String() {
+ case "map[string]interface {}":
+ err = json.Unmarshal(actor, &nActor)
+ CheckError(err, "error with To []interface{}")
+
+ case "string":
+ var str string
+ err = json.Unmarshal(actor, &str)
+ CheckError(err, "error with To string")
+ nActor.Id = str
+ }
+
+ return nActor
+ }
+
+ return nActor
+}
+
+func GetToFromJson(to []byte) []string {
+ var generic interface{}
+
+ err := json.Unmarshal(to, &generic)
+
+ if err != nil {
+ return nil
+ }
+
+ t := reflect.TypeOf(generic)
+
+ if t != nil {
+ var nStr []string
+ switch t.String() {
+ case "[]interface {}":
+ err = json.Unmarshal(to, &nStr)
+ CheckError(err, "error with To []interface{}")
+ return nStr
+
+ case "string":
+ var str string
+ err = json.Unmarshal(to, &str)
+ CheckError(err, "error with To string")
+ nStr = append(nStr, str)
+ return nStr
+ }
+ }
+
+ return nil
+}
+
+func HasContextFromJson(context []byte) bool {
+ var generic interface{}
+
+ err := json.Unmarshal(context, &generic)
+
+ CheckError(err, "error with getting context")
+
+ t := reflect.TypeOf(generic)
+
+ hasContext := false
+
+ switch t.String() {
+ case "[]interface {}":
+ var arrContext AtContextArray
+ err = json.Unmarshal(context, &arrContext.Context)
+ CheckError(err, "error with []interface{}")
+ if len(arrContext.Context) > 0 {
+ if arrContext.Context[0] == "https://www.w3.org/ns/activitystreams" {
+ hasContext = true
+ }
+ }
+ case "string":
+ var arrContext AtContextString
+ err = json.Unmarshal(context, &arrContext.Context)
+ CheckError(err, "error with string")
+ if arrContext.Context == "https://www.w3.org/ns/activitystreams" {
+ hasContext = true
+ }
+ }
+
+ return hasContext
+}
+
+func ObjectFromForm(r *http.Request, db *sql.DB, obj ObjectBase) ObjectBase {
+
+ file, header, _ := r.FormFile("file")
+
+ if file != nil {
+ defer file.Close()
+
+ var tempFile = new(os.File)
+ obj.Attachment, tempFile = CreateAttachmentObject(file, header)
+
+ defer tempFile.Close();
+
+ fileBytes, _ := ioutil.ReadAll(file)
+
+ tempFile.Write(fileBytes)
+ }
+
+ obj.AttributedTo = EscapeString(r.FormValue("name"))
+ obj.Name = EscapeString(r.FormValue("subject"))
+ obj.Content = EscapeString(r.FormValue("comment"))
+
+ obj = ParseOptions(r, obj)
+
+ var originalPost ObjectBase
+ originalPost.Id = EscapeString(r.FormValue("inReplyTo"))
+
+ obj.InReplyTo = append(obj.InReplyTo, originalPost)
+
+ var activity Activity
+
+ activity.To = append(activity.To, originalPost.Id)
+
+ if originalPost.Id != "" {
+ if !IsActivityLocal(db, activity) {
+ id := GetActorFromID(originalPost.Id).Id
+
+ obj.To = append(obj.To, GetActor(id).Id)
+ }
+ }
+
+ replyingTo := ParseCommentForReplies(r.FormValue("comment"))
+
+ for _, e := range replyingTo {
+
+ has := false
+
+ for _, f := range obj.InReplyTo {
+ if e.Id == f.Id {
+ has = true
+ break
+ }
+ }
+
+ if !has {
+ obj.InReplyTo = append(obj.InReplyTo, e)
+
+ var activity Activity
+
+ activity.To = append(activity.To, e.Id)
+
+ if !IsActivityLocal(db, activity) {
+ id := GetActorFromID(e.Id).Id
+
+ obj.To = append(obj.To, GetActor(id).Id)
+ }
+ }
+ }
+
+ return obj
+}
+
+
+func ParseOptions(r *http.Request, obj ObjectBase) ObjectBase {
+ options := EscapeString(r.FormValue("options"))
+ if options != "" {
+ option := strings.Split(options, ";")
+ email := regexp.MustCompile(".+@.+\\..+")
+ wallet := regexp.MustCompile("wallet:.+")
+ delete := regexp.MustCompile("delete:.+")
+ for _, e := range option {
+ if e == "noko" {
+ obj.Option = append(obj.Option, "noko")
+ } else if e == "sage" {
+ obj.Option = append(obj.Option, "sage")
+ } else if e == "nokosage" {
+ obj.Option = append(obj.Option, "nokosage")
+ } else if email.MatchString(e) {
+ obj.Option = append(obj.Option, "email:" + e)
+ } else if wallet.MatchString(e) {
+ obj.Option = append(obj.Option, "wallet")
+ var wallet CryptoCur
+ value := strings.Split(e, ":")
+ wallet.Type = value[0]
+ wallet.Address = value[1]
+ obj.Wallet = append(obj.Wallet, wallet)
+ } else if delete.MatchString(e) {
+ obj.Option = append(obj.Option, e)
+ }
+ }
+ }
+
+ return obj
+}
+
+func GetActivityFromJson(r *http.Request, db *sql.DB) Activity {
+ body, _ := ioutil.ReadAll(r.Body)
+
+ var respActivity ActivityRaw
+
+ var nActivity Activity
+
+ var nType string
+
+ err := json.Unmarshal(body, &respActivity)
+
+ CheckError(err, "error with activity from json")
+
+ if HasContextFromJson(respActivity.AtContextRaw.Context) {
+ var jObj ObjectBase
+
+ if respActivity.Type == "Note" {
+ jObj = GetObjectFromJson(body)
+ jObj.AtContext.Context = ""
+ nType = "Create"
+ } else {
+ jObj = GetObjectFromJson(respActivity.ObjectRaw)
+ nType = respActivity.Type
+ }
+
+ actor := GetActorFromJson(respActivity.ActorRaw)
+ to := GetToFromJson(respActivity.ToRaw)
+ cc := GetToFromJson(respActivity.CcRaw)
+
+ jObj.AttributedTo = actor.Id
+
+ nActivity.AtContext.Context = "https://www.w3.org/ns/activitystreams"
+ nActivity.Type = nType
+ nActivity.Actor = &actor
+ nActivity.Published = respActivity.Published
+ nActivity.Auth = respActivity.Auth
+
+ if len(to) > 0 {
+ nActivity.To = to
+ }
+
+ if len(cc) > 0 {
+ nActivity.Cc = cc
+ }
+
+ nActivity.Name = respActivity.Name
+ nActivity.Object = &jObj
+ }
+
+ return nActivity
+}
+
+func CheckCaptcha(db *sql.DB, captcha string) bool {
+ parts := strings.Split(captcha, ":")
+ path := "public/" + parts[0] + ".png"
+ code := GetCaptchaCodeDB(db, path)
+
+ DeleteCaptchaCodeDB(db, path)
+ CreateNewCaptcha(db)
+
+ if (code == strings.ToUpper(parts[1])) {
+ return true
+ }
+
+ return false
+}
+
+func ParseInboxRequest(w http.ResponseWriter, r *http.Request, db *sql.DB) {
+ activity := GetActivityFromJson(r, db)
+
+ switch(activity.Type) {
+ case "Create":
+ for _, e := range activity.Object.InReplyTo {
+ if IsObjectLocal(db, e.Id) {
+ WriteObjectReplyToLocalDB(db, activity.Object.Id, e.Id)
+ }
+ }
+ break
+
+ case "Follow":
+ for _, e := range activity.To {
+ if IsObjectLocal(db, e) {
+ nActivity := SetActorFollowingDB(db, activity)
+ j, _ := json.Marshal(&nActivity)
+ w.Write([]byte(j))
+ }
+ }
+ break
+ }
+}
+
+func MakeActivityFollowingReq(w http.ResponseWriter, r *http.Request, activity Activity) bool {
+ actor := GetActor(activity.Object.Id)
+
+ resp, err := http.NewRequest("POST", actor.Inbox, nil)
+
+ CheckError(err, "Cannot make new get request to actor inbox for following req")
+
+ defer resp.Body.Close()
+
+ body, _ := ioutil.ReadAll(resp.Body)
+
+ var respActivity Activity
+
+ err = json.Unmarshal(body, &respActivity)
+
+ if respActivity.Type == "Accept" {
+ return true
+ }
+
+ return false
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/README.md
diff --git a/activityPubStruct.go b/activityPubStruct.go
new file mode 100644
index 0000000..81045f6
--- /dev/null
+++ b/activityPubStruct.go
@@ -0,0 +1,191 @@
+package main
+
+import "encoding/json"
+
+type AtContextRaw struct {
+ Context json.RawMessage `json:"@context,omitempty"`
+}
+
+type ActivityRaw struct {
+ AtContextRaw
+ Type string `json:"type,omitempty"`
+ Id string `json:"id,omitempty"`
+ Name string `json:"name,omitempty"`
+ Summary string `json:"summary,omitempty"`
+ Auth string `json:"auth,omitempty"`
+ ToRaw json.RawMessage `json:"to,omitempty"`
+ BtoRaw json.RawMessage `json:"bto,omitempty"`
+ CcRaw json.RawMessage `json:"cc,omitempty"`
+ Published string `json:"published,omitempty"`
+ ActorRaw json.RawMessage `json:"actor,omitempty"`
+ ObjectRaw json.RawMessage `json:"object,omitempty"`
+}
+
+type AtContext struct {
+ Context string `json:"@context,omitempty"`
+}
+
+type AtContextArray struct {
+ Context []interface {} `json:"@context,omitempty"`
+}
+
+type AtContextString struct {
+ Context string `json:"@context,omitempty"`
+}
+
+type ActorString struct {
+ Actor string `json:"actor,omitempty"`
+}
+
+type ObjectArray struct {
+ Object []ObjectBase `json:"object,omitempty"`
+}
+
+type Object struct {
+ Object *ObjectBase `json:"object,omitempty"`
+}
+
+type ObjectString struct {
+ Object string `json:"object,omitempty"`
+}
+
+type ToArray struct {
+ To []string `json:"to,omitempty"`
+}
+
+type ToString struct {
+ To string `json:"to,omitempty"`
+}
+
+type CcArray struct {
+ Cc []string `json:"cc,omitempty"`
+}
+
+type CcOjectString struct {
+ Cc string `json:"cc,omitempty"`
+}
+
+type Actor struct {
+ AtContext
+ Type string `json:"type,omitempty"`
+ Id string `json:"id,omitempty"`
+ Inbox string `json:"inbox,omitempty"`
+ Outbox string `json:"outbox,omitempty"`
+ Following string `json:"following,omitempty"`
+ Followers string `json:"followers,omitempty"`
+ Name string `json:"name,omitempty"`
+ PreferredUsername string `json:"prefereedUsername,omitempty"`
+ Summary string `json:"summary,omitempty"`
+ AuthRequirement []string `json:"authrequirement,omitempty"`
+ Restricted bool `json:"restricted,omitempty"`
+}
+
+type Activity struct {
+ AtContext
+ Type string `json:"type,omitempty"`
+ Id string `json:"id,omitempty"`
+ Actor *Actor `json:"actor,omitempty"`
+ Name string `json:"name,omitempty"`
+ Summary string `json:"summary,omitempty"`
+ Auth string `json:"auth,omitempty"`
+ To []string `json:"to, omitempty"`
+ Bto []string `json:"bto,omitempty"`
+ Cc []string `json:"cc, omitempty"`
+ Published string `json:"published,omitempty"`
+ Object *ObjectBase `json:"object, omitempty"`
+}
+
+type ObjectBase struct {
+ AtContext
+ Type string `json:"type,omitempty"`
+ Id string `json:"id,omitempty"`
+ Name string `json:"name,omitempty"`
+ Option []string `json:"option,omitempty"`
+ Alias string `json:"alias,omitempty"`
+ AttributedTo string `json:"attributedTo,omitempty"`
+ Actor *Actor `json:"actor,omitempty"`
+ Audience string `json:"audience,omitempty"`
+ Content string `json:"content,omitempty"`
+ EndTime string `json:"endTime,omitempty"`
+ Generator string `json:"generator,omitempty"`
+ Icon string `json:"icon,omitempty"`
+ Image string `json:"image,omitempty"`
+ InReplyTo []ObjectBase `json:"inReplyTo,omitempty"`
+ Location string `json:"location,omitempty"`
+ Preview string `json:"preview,omitempty"`
+ Published string `json:"published,omitempty"`
+ Updated string `json:"updated,omitempty"`
+ Object *NestedObjectBase `json:"object,omitempty"`
+ Attachment []ObjectBase `json:"attachment,omitempty"`
+ Replies *CollectionBase `json:"replies,omitempty"`
+ StartTime string `json:"startTime,omitempty"`
+ Summary string `json:"summary,omitempty"`
+ Tag []ObjectBase `json:"tag,omitempty"`
+ Wallet []CryptoCur `json:"wallet,omitempty"`
+ Deleted string `json:"deleted,omitempty"`
+ Url []ObjectBase `json:"url,omitempty"`
+ Href string `json:"href,omitempty"`
+ To []string `json:"to,omitempty"`
+ Bto []string `json:"bto,omitempty"`
+ Cc []string `json:"cc,omitempty"`
+ Bcc string `json:"Bcc,omitempty"`
+ MediaType string `json:"mediatype,omitempty"`
+ Duration string `json:"duration,omitempty"`
+ Size int64 `json:"size,omitempty"`
+}
+
+type CryptoCur struct {
+ Type string `json:"type,omitempty"`
+ Address string `json:"address,omitempty"`
+}
+
+type NestedObjectBase struct {
+ AtContext
+ Type string `json:"type,omitempty"`
+ Id string `json:"id,omitempty"`
+ Name string `json:"name,omitempty"`
+ Alias string `json:"alias,omitempty"`
+ AttributedTo string `json:"attributedTo,omitempty"`
+ Actor *Actor `json:"actor,omitempty"`
+ Audience string `json:"audience,omitempty"`
+ Content string `json:"content,omitempty"`
+ EndTime string `json:"endTime,omitempty"`
+ Generator string `json:"generator,omitempty"`
+ Icon string `json:"icon,omitempty"`
+ Image string `json:"image,omitempty"`
+ InReplyTo []ObjectBase `json:"inReplyTo,omitempty"`
+ Location string `json:"location,omitempty"`
+ Preview string `json:"preview,omitempty"`
+ Published string `json:"published,omitempty"`
+ Attachment []ObjectBase `json:"attachment,omitempty"`
+ Replies *CollectionBase `json:"replies,omitempty"`
+ StartTime string `json:"startTime,omitempty"`
+ Summary string `json:"summary,omitempty"`
+ Tag []ObjectBase `json:"tag,omitempty"`
+ Updated string `json:"updated,omitempty"`
+ Deleted string `json:"deleted,omitempty"`
+ Url []ObjectBase `json:"url,omitempty"`
+ Href string `json:"href,omitempty"`
+ To []string `json:"to,omitempty"`
+ Bto []string `json:"bto,omitempty"`
+ Cc []string `json:"cc,omitempty"`
+ Bcc string `json:"Bcc,omitempty"`
+ MediaType string `json:"mediatype,omitempty"`
+ Duration string `json:"duration,omitempty"`
+ Size int64 `json:"size,omitempty"`
+}
+
+type CollectionBase struct {
+ Actor string `json:"actor,omitempty"`
+ Summary string `json:"summary,omitempty"`
+ Type string `json:"type,omitempty"`
+ TotalItems int `json:"totalItems,omitempty"`
+ TotalImgs int `json:"totalImgs,omitempty"`
+ OrderedItems []ObjectBase `json:"orderedItems,omitempty"`
+ Items []ObjectBase `json:"items,omitempty"`
+}
+
+type Collection struct {
+ AtContext
+ CollectionBase
+}
diff --git a/databaseschema.psql b/databaseschema.psql
new file mode 100644
index 0000000..e4c3fe1
--- /dev/null
+++ b/databaseschema.psql
@@ -0,0 +1,135 @@
+CREATE TABLE IF NOT EXISTS actor(
+type varchar(50) default '',
+id varchar(100) UNIQUE PRIMARY KEY,
+name varchar(50) default '',
+preferedusername varchar(100) default '',
+summary varchar(200) default '',
+inbox varchar(100) default '',
+outbox varchar(100) default '',
+following varchar(100) default '',
+followers varchar(100) default '',
+restricted boolean default false
+);
+
+CREATE TABLE IF NOT EXISTS replies(
+id varchar(100),
+inreplyto varchar(100)
+);
+
+CREATE TABLE IF NOT EXISTS following(
+id varchar(100),
+following varchar(100)
+);
+
+CREATE TABLE IF NOT EXISTS follower(
+id varchar(100),
+follower varchar(100)
+);
+
+CREATE TABLE IF NOT EXISTS verification(
+type varchar(50) default '',
+identifier varchar(50),
+code varchar(50),
+created TIMESTAMP default NOW()
+);
+
+CREATE TABLE IF NOT EXISTS reported(
+id varchar(100),
+count int,
+board varchar(100)
+);
+
+CREATE TABLE IF NOT EXISTS verificationcooldown(
+code varchar(50),
+created TIMESTAMP default NOW()
+);
+
+CREATE TABLE IF NOT EXISTS boardaccess(
+identifier varchar(100),
+code varchar(50),
+board varchar(100),
+type varchar(50)
+);
+
+CREATE TABLE IF NOT EXISTS crossverification(
+verificationcode varchar(50),
+code varchar(50)
+);
+
+CREATE TABLE IF NOT EXISTS actorauth(
+type varchar(50),
+board varchar(100)
+);
+
+CREATE TABLE IF NOT EXISTS wallet(
+id varchar(100) UNIQUE PRIMARY KEY,
+type varchar(25),
+address varchar(50)
+);
+
+CREATE TABLE IF NOT EXISTS activitystream(
+actor varchar(100) default '',
+attachment varchar(100) default '',
+attributedTo varchar(100) default '',
+audience varchar(100) default '',
+bcc varchar(100) default '',
+bto varchar(100) default '',
+cc varchar(100) default '',
+context varchar(100) default '',
+current varchar(100) default '',
+first varchar(100) default '',
+generator varchar(100) default '',
+icon varchar(100) default '',
+id varchar(100) UNIQUE PRIMARY KEY,
+image varchar(100) default '',
+instrument varchar(100) default '',
+last varchar(100) default '',
+location varchar(100) default '',
+items varchar(100) default '',
+oneOf varchar(100) default '',
+anyOf varchar(100) default '',
+closed varchar(100) default '',
+origin varchar(100) default '',
+next varchar(100) default '',
+object varchar(100),
+prev varchar(100) default '',
+preview varchar(100) default '',
+result varchar(100) default '',
+tag varchar(100) default '',
+target varchar(100) default '',
+type varchar(100) default '',
+to_ varchar(100) default '',
+url varchar(100) default '',
+accuracy varchar(100) default '',
+altitude varchar(100) default '',
+content varchar(2000) default '',
+name varchar(100) default '',
+alias varchar(100) default '',
+duration varchar(100) default '',
+height varchar(100) default '',
+href varchar(100) default '',
+hreflang varchar(100) default '',
+partOf varchar(100) default '',
+latitude varchar(100) default '',
+longitude varchar(100) default '',
+mediaType varchar(100) default '',
+endTime varchar(100) default '',
+published TIMESTAMP default NOW(),
+startTime varchar(100) default '',
+radius varchar(100) default '',
+rel varchar(100) default '',
+startIndex varchar(100) default '',
+summary varchar(100) default '',
+totalItems varchar(100) default '',
+units varchar(100) default '',
+updated TIMESTAMP default NOW(),
+deleted TIMESTAMP default NULL,
+width varchar(100) default '',
+subject varchar(100) default '',
+relationship varchar(100) default '',
+describes varchar(100) default '',
+formerType varchar(100) default '',
+size int default NULL,
+public boolean default false,
+CONSTRAINT fk_object FOREIGN KEY (object) REFERENCES activitystream(id)
+); \ No newline at end of file
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..7ee8a84
--- /dev/null
+++ b/main.go
@@ -0,0 +1,937 @@
+package main
+
+import "fmt"
+import "strings"
+import "net/http"
+import "net/url"
+import "database/sql"
+import _ "github.com/lib/pq"
+import "math/rand"
+import "time"
+import "regexp"
+import "os/exec"
+import "bytes"
+import "encoding/json"
+import "io/ioutil"
+import "mime/multipart"
+import "os"
+
+const (
+ host = "localhost"
+ port = 5432
+ user = "postgres"
+ password = "password"
+ dbname = "fchan_server"
+)
+
+var Port = ":3000"
+var TP = "https://"
+var Domain = TP + "server.fchan.xyz"
+
+var authReq = []string{"captcha","email","passphrase"}
+
+var supportedFiles = []string{"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"}
+
+
+var SiteEmail string //contact@fchan.xyz
+var SiteEmailPassword string
+var SiteEmailServer string //mail.fchan.xyz
+var SiteEmailPort string //587
+
+type BoardAccess struct {
+ boards []string
+}
+
+func main() {
+
+ if _, err := os.Stat("./public"); os.IsNotExist(err) {
+ os.Mkdir("./public", os.ModeDir)
+ }
+
+ db := ConnectDB();
+
+ defer db.Close()
+
+ go MakeCaptchas(db, 100)
+
+ // root actor is used to follow remote feeds that are not local
+ //name, prefname, summary, auth requirements, restricted
+ CreateNewBoardDB(db, *CreateNewActor("", "FChan", "FChan is a federated image board instance.", authReq, false))
+
+ // Allow access to public media folder
+ fileServer := http.FileServer(http.Dir("./public"))
+ http.Handle("/public/", http.StripPrefix("/public", neuter(fileServer)))
+
+ // main routing
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
+ path := r.URL.Path
+
+ // remove trailing slash
+ if path != "/" {
+ re := regexp.MustCompile(`/$`)
+ path = re.ReplaceAllString(path, "")
+ }
+
+ method := r.Method
+
+ actor := GetActorFromPath(db, path, "/")
+
+ var mainActor bool
+ var mainInbox bool
+ var mainOutbox bool
+ var mainFollowing bool
+ var mainFollowers bool
+
+ var actorMain bool
+ var actorInbox bool
+ var actorOutbox bool
+ var actorFollowing bool
+ var actorFollowers bool
+ var actorReported bool
+ var actorVerification bool
+
+
+ if(actor.Id != ""){
+ if actor.Name == "main" {
+ mainActor = (path == "/")
+ mainInbox = (path == "/inbox")
+ mainOutbox = (path == "/outbox")
+ mainFollowing = (path == "/following")
+ mainFollowers = (path == "/followers")
+ } else {
+ actorMain = (path == "/" + actor.Name)
+ actorInbox = (path == "/" + actor.Name + "/inbox")
+ actorOutbox = (path == "/" + actor.Name + "/outbox")
+ actorFollowing = (path == "/" + actor.Name + "/following")
+ actorFollowers = (path == "/" + actor.Name + "/followers")
+ actorReported = (path == "/" + actor.Name + "/reported")
+ actorVerification = (path == "/" + actor.Name + "/verification")
+ }
+ }
+
+
+ if mainActor {
+ GetActorInfo(w, db, Domain)
+ } else if mainInbox {
+ if method == "POST" {
+
+ } else {
+ w.WriteHeader(http.StatusForbidden)
+ w.Write([]byte("404 no path"))
+ }
+ } else if mainOutbox {
+ if method == "GET" {
+ GetActorOutbox(w, r, db)
+ } else if method == "POST" {
+ ParseOutboxRequest(w, r, db)
+ } else {
+ w.WriteHeader(http.StatusForbidden)
+ w.Write([]byte("404 no path"))
+ }
+ } else if mainFollowing {
+ GetActorFollowing(w, db, Domain)
+ } else if mainFollowers {
+ GetActorFollowers(w, db, Domain)
+ } else if actorMain {
+ GetActorInfo(w, db, actor.Id)
+ } else if actorInbox {
+ if method == "POST" {
+ ParseInboxRequest(w, r, db)
+ } else {
+ w.WriteHeader(http.StatusForbidden)
+ w.Write([]byte("404 no path"))
+ }
+ } else if actorOutbox {
+ if method == "GET" {
+ GetActorOutbox(w, r, db)
+ } else if method == "POST" {
+ ParseOutboxRequest(w, r, db)
+ } else {
+ w.WriteHeader(http.StatusForbidden)
+ w.Write([]byte("404 no path"))
+ }
+ } else if actorFollowing {
+ GetActorFollowing(w, db, actor.Id)
+ } else if actorFollowers {
+ GetActorFollowers(w, db, actor.Id)
+ } else if actorReported {
+ GetActorReported(w, r, db, actor.Id)
+ } else if actorVerification {
+ if method == "POST" {
+ p, _ := url.ParseQuery(r.URL.RawQuery)
+ if len(p["email"]) > 0 {
+ email := p["email"][0]
+ verify := GetVerificationByEmail(db, email)
+ if verify.Identifier != "" || !IsEmailSetup() {
+ w.WriteHeader(http.StatusForbidden)
+ w.Write([]byte("400 no path"))
+ } else {
+ var nVerify Verify
+ nVerify.Type = "email"
+ nVerify.Identifier = email
+ nVerify.Code = CreateKey(32)
+ nVerify.Board = actor.Id
+ CreateVerification(db, nVerify)
+ SendVerification(nVerify)
+ w.WriteHeader(http.StatusCreated)
+ w.Write([]byte("Verification added"))
+ }
+
+ } else {
+ w.WriteHeader(http.StatusForbidden)
+ w.Write([]byte("400 no path"))
+ }
+ } else {
+ w.WriteHeader(http.StatusForbidden)
+ w.Write([]byte("400 no path"))
+ }
+ } else {
+ collection := GetCollectionFromPath(db, Domain + "" + path)
+ if len(collection.OrderedItems) > 0 {
+ enc, _ := json.MarshalIndent(collection, "", "\t")
+ w.Header().Set("Content-Type", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"")
+ w.Write(enc)
+ } else {
+ w.WriteHeader(http.StatusForbidden)
+ w.Write([]byte("404 no path"))
+ }
+ }
+ })
+
+ http.HandleFunc("/getcaptcha", func(w http.ResponseWriter, r *http.Request){
+ w.Write([]byte(GetRandomCaptcha(db)))
+ })
+
+ http.HandleFunc("/delete", func(w http.ResponseWriter, r *http.Request){
+ values := r.URL.Query().Get("id")
+
+ if len(values) < 1 || !IsIDLocal(db, values) {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ return
+ }
+
+ id := values
+ DeleteObject(db, id)
+ w.Write([]byte(""))
+
+ })
+
+ http.HandleFunc("/deleteattach", func(w http.ResponseWriter, r *http.Request){
+
+ values := r.URL.Query().Get("id")
+
+ header := r.Header.Get("Authorization")
+
+ auth := strings.Split(header, " ")
+
+ if len(values) < 1 || len(auth) < 2 {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ return
+ }
+
+ id := values
+
+ if !IsIDLocal(db, id) {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ return
+ }
+
+ actor := GetActorFromPath(db, id, "/")
+
+ if HasAuth(db, auth[1], actor.Id) {
+ DeleteAttachmentFromFile(db, id)
+ DeleteAttachmentFromDB(db, id)
+ w.Write([]byte(""))
+ return
+ }
+
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ })
+
+ http.HandleFunc("/report", func(w http.ResponseWriter, r *http.Request){
+
+ id := r.URL.Query().Get("id")
+ close := r.URL.Query().Get("close")
+
+ if close == "1" {
+ if !IsIDLocal(db, id) {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ return
+ }
+
+ reported := DeleteReportActivity(db, id)
+ if reported {
+ w.Write([]byte(""))
+ return
+ }
+ }
+
+ reported := ReportActivity(db, id)
+
+ if reported {
+ w.Write([]byte(""))
+ return
+ }
+
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+
+ })
+
+ http.HandleFunc("/verify", func(w http.ResponseWriter, r *http.Request){
+ var verify Verify
+ defer r.Body.Close()
+
+ body, _ := ioutil.ReadAll(r.Body)
+
+ err := json.Unmarshal(body, &verify)
+
+ CheckError(err, "error get verify from json")
+
+ v := GetVerificationByCode(db, verify.Code)
+
+ if v.Identifier == verify.Identifier {
+ w.Write([]byte(v.Board))
+ return
+ }
+
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ })
+
+ http.ListenAndServe(Port, nil)
+}
+
+func CheckError(e error, m string) error{
+ if e != nil {
+ fmt.Println(m)
+ panic(e)
+ }
+
+ return e
+}
+
+func ConnectDB() *sql.DB {
+ psqlInfo := fmt.Sprintf("host=%s port=%d user=%s password=%s " +
+ "dbname=%s sslmode=disable", host, port, user, password, dbname)
+
+ db, err := sql.Open("postgres", psqlInfo)
+ CheckError(err, "error with db connection")
+
+ err = db.Ping()
+
+ CheckError(err, "error with db ping")
+
+ fmt.Println("Successfully connected DB")
+ return db
+}
+
+func CreateKey(len int) string {
+ var key string
+ str := (CreateTripCode(RandomID(len)))
+ for i := 0; i < len; i++ {
+ key += fmt.Sprintf("%c", str[i])
+ }
+ return key
+}
+
+func neuter(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if strings.HasSuffix(r.URL.Path, "/") {
+ http.NotFound(w, r)
+ return
+ }
+
+ next.ServeHTTP(w, r)
+ })
+}
+
+func CreateTripCode(input string) string {
+ cmd := exec.Command("sha512sum")
+ cmd.Stdin = strings.NewReader(input)
+ var out bytes.Buffer
+ cmd.Stdout = &out
+ err := cmd.Run()
+
+ CheckError(err, "error with create trip code")
+
+ return out.String()
+}
+
+func GetActorFromPath(db *sql.DB, location string, prefix string) Actor {
+ pattern := fmt.Sprintf("%s([^/\n]+)(/.+)?", prefix)
+ re := regexp.MustCompile(pattern)
+ match := re.FindStringSubmatch(location)
+
+ var actor string
+
+ if(len(match) < 1 ) {
+ actor = "/"
+ } else {
+ actor = strings.Replace(match[1], "/", "", -1)
+ }
+
+ if actor == "/" || actor == "outbox" || actor == "inbox" || actor == "following" || actor == "followers" {
+ actor = Domain
+ } else {
+ actor = Domain + "/" + actor
+ }
+
+ var nActor Actor
+
+ nActor = GetActorFromDB(db, actor)
+
+ return nActor
+}
+
+func GetContentType(location string) string {
+ elements := strings.Split(location, ";")
+ if len(elements) > 0 {
+ return elements[0]
+ } else {
+ return location
+ }
+}
+
+func RandomID(size int) string {
+ rand.Seed(time.Now().UnixNano())
+ domain := "0123456789ABCDEF"
+ rng := size
+ newID := ""
+ for i := 0; i < rng; i++ {
+ newID += string(domain[rand.Intn(len(domain))])
+ }
+
+ return newID
+}
+
+func CreateUniqueID(db *sql.DB, actor string) string {
+ var newID string
+ isUnique := false
+ for !isUnique {
+ newID = RandomID(8)
+
+ query := fmt.Sprintf("select id from activitystream where id='%s/%s/%s'", Domain, actor, newID)
+
+ rows, err := db.Query(query)
+
+ CheckError(err, "error with unique id query")
+
+ defer rows.Close()
+
+ var count int = 0
+ for rows.Next(){
+ count += 1
+ }
+
+ if count < 1 {
+ isUnique = true
+ }
+ }
+
+ return newID
+}
+
+func CreateNewActor(board string, prefName string, summary string, authReq []string, restricted bool) *Actor{
+ actor := new(Actor)
+
+ var path string
+ if board == "" {
+ path = Domain
+ actor.Name = "main"
+ } else {
+ path = Domain + "/" + board
+ actor.Name = board
+ }
+
+ actor.Type = "Service"
+ actor.Id = fmt.Sprintf("%s", path)
+ actor.Following = fmt.Sprintf("%s/following", actor.Id)
+ actor.Followers = fmt.Sprintf("%s/followers", actor.Id)
+ actor.Inbox = fmt.Sprintf("%s/inbox", actor.Id)
+ actor.Outbox = fmt.Sprintf("%s/outbox", actor.Id)
+ actor.PreferredUsername = prefName
+ actor.Restricted = restricted
+ actor.Summary = summary
+ actor.AuthRequirement = authReq
+
+ return actor
+}
+
+func GetActorInfo(w http.ResponseWriter, db *sql.DB, id string) {
+ actor := GetActorFromDB(db, id)
+ enc, _ := json.MarshalIndent(actor, "", "\t")
+ w.Header().Set("Content-Type", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"")
+ w.Write(enc)
+}
+
+func CreateObject(objType string) ObjectBase {
+ var nObj ObjectBase
+
+ nObj.Type = objType
+ nObj.Published = time.Now().Format(time.RFC3339)
+ nObj.Updated = time.Now().Format(time.RFC3339)
+
+ return nObj
+}
+
+func CreateActivity(activityType string, obj ObjectBase) Activity {
+ var newActivity Activity
+
+ newActivity.AtContext.Context = "https://www.w3.org/ns/activitystreams"
+ newActivity.Type = activityType
+ newActivity.Published = obj.Published
+ newActivity.Actor = obj.Actor
+ newActivity.Object = &obj
+
+ for _, e := range obj.To {
+ newActivity.To = append(newActivity.To, e)
+ }
+
+ for _, e := range obj.Cc {
+ newActivity.Cc = append(newActivity.Cc, e)
+ }
+
+ return newActivity
+}
+
+func ProcessActivity(db *sql.DB, activity Activity) {
+ activityType := activity.Type
+
+ if activityType == "Create" {
+ for _, e := range activity.To {
+ if GetActorFromDB(db, e).Id != "" {
+ fmt.Println("actor is in the database")
+ } else {
+ fmt.Println("actor is NOT in the database")
+ }
+ }
+ } else if activityType == "Follow" {
+
+ } else if activityType == "Delete" {
+
+ }
+}
+
+func CreateAttachmentObject(file multipart.File, header *multipart.FileHeader) ([]ObjectBase, *os.File) {
+ contentType, _ := GetFileContentType(file)
+ filename := header.Filename
+ size := header.Size
+
+ re := regexp.MustCompile(`.+/`)
+
+ fileType := re.ReplaceAllString(contentType, "")
+
+ tempFile, _ := ioutil.TempFile("./public", "*." + fileType)
+
+ var nAttachment []ObjectBase
+ var image ObjectBase
+
+ image.Type = "Attachment"
+ image.Name = filename
+ image.Href = Domain + "/" + tempFile.Name()
+ image.MediaType = contentType
+ image.Size = size
+ image.Published = time.Now().Format(time.RFC3339)
+
+ nAttachment = append(nAttachment, image)
+
+ return nAttachment, tempFile
+}
+
+func ParseCommentForReplies(comment string) []ObjectBase {
+
+ re := regexp.MustCompile("(>>)(https://|http://)?(www\\.)?.+\\/\\w+")
+ match := re.FindAllStringSubmatch(comment, -1)
+
+ var links []string
+
+ for i:= 0; i < len(match); i++ {
+ str := strings.Replace(match[i][0], ">>", "", 1)
+ str = strings.Replace(str, "www.", "", 1)
+ str = strings.Replace(str, "http://", "", 1)
+ str = TP + "://" + str
+ links = append(links, str)
+ }
+
+ var validLinks []ObjectBase
+ for i:= 0; i < len(links); i++ {
+ _, isValid := CheckValidActivity(links[i])
+ if(isValid) {
+ var reply = new(ObjectBase)
+ reply.Id = links[i]
+ reply.Published = time.Now().Format(time.RFC3339)
+ validLinks = append(validLinks, *reply)
+ }
+ }
+
+ return validLinks
+}
+
+
+func CheckValidActivity(id string) (Collection, bool) {
+
+ req, err := http.NewRequest("GET", id, nil)
+
+ if err != nil {
+ fmt.Println("error with request")
+ panic(err)
+ }
+
+ req.Header.Set("Accept", "json/application/activity+json")
+
+ resp, err := http.DefaultClient.Do(req)
+
+ if err != nil {
+ fmt.Println("error with response")
+ panic(err)
+ }
+
+ defer resp.Body.Close()
+
+ body, _ := ioutil.ReadAll(resp.Body)
+
+ var respCollection Collection
+
+ err = json.Unmarshal(body, &respCollection)
+
+ if err != nil {
+ panic(err)
+ }
+
+ if respCollection.AtContext.Context == "https://www.w3.org/ns/activitystreams" && respCollection.OrderedItems[0].Id != "" {
+ return respCollection, true;
+ }
+
+ return respCollection, false;
+}
+
+func GetActor(id string) Actor {
+
+ var respActor Actor
+
+ req, err := http.NewRequest("GET", id, nil)
+
+ CheckError(err, "error with getting actor req")
+
+ resp, err := http.DefaultClient.Do(req)
+
+ CheckError(err, "error with getting actor resp")
+
+ defer resp.Body.Close()
+
+ body, _ := ioutil.ReadAll(resp.Body)
+
+ err = json.Unmarshal(body, &respActor)
+
+ CheckError(err, "error getting actor from body")
+
+ return respActor
+}
+
+func GetActorCollection(collection string) Collection {
+ var nCollection Collection
+
+ req, err := http.NewRequest("GET", collection, nil)
+
+ CheckError(err, "error with getting actor collection req " + collection)
+
+ resp, err := http.DefaultClient.Do(req)
+
+ CheckError(err, "error with getting actor collection resp " + collection)
+
+ if resp.StatusCode == 200 {
+
+ defer resp.Body.Close()
+
+ body, _ := ioutil.ReadAll(resp.Body)
+
+ err = json.Unmarshal(body, &nCollection)
+
+ CheckError(err, "error getting actor collection from body " + collection)
+ }
+
+ return nCollection
+}
+
+
+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", "json/application/activity+json")
+
+ resp, err := http.DefaultClient.Do(req)
+
+ CheckError(err, "error with valid actor response")
+
+ if resp.StatusCode == 403 {
+ return respCollection, false;
+ }
+
+ defer resp.Body.Close()
+
+ body, _ := ioutil.ReadAll(resp.Body)
+
+ err = json.Unmarshal(body, &respCollection)
+
+ if err != nil {
+ panic(err)
+ }
+
+ if respCollection.AtContext.Context == "https://www.w3.org/ns/activitystreams" && respCollection.Id != "" && respCollection.Inbox != "" && respCollection.Outbox != "" {
+ return respCollection, true;
+ }
+
+ return respCollection, false;
+}
+
+
+
+func IsActivityLocal(db *sql.DB, activity Activity) bool {
+ for _, e := range activity.To {
+ if GetActorFromDB(db, e).Id != "" {
+ return true
+ }
+ }
+
+ for _, e := range activity.Cc {
+ if GetActorFromDB(db, e).Id != "" {
+ return true
+ }
+ }
+
+ if activity.Actor != nil && GetActorFromDB(db, activity.Actor.Id).Id != "" {
+ return true
+ }
+
+ return false
+}
+
+func IsIDLocal(db *sql.DB, id string) bool {
+
+ if GetActivityFromDB(db, id).OrderedItems != nil {
+ return true
+ }
+
+ return false
+}
+
+func IsObjectLocal(db *sql.DB, id string) bool {
+
+ query := fmt.Sprintf("select id from activitystream where id='%s'", id)
+
+ rows, err := db.Query(query)
+
+ defer rows.Close()
+
+ if err != nil {
+ return false
+ }
+
+ return true
+}
+
+func GetObjectFromActivity(activity Activity) ObjectBase {
+ return *activity.Object
+}
+
+func MakeCaptchas(db *sql.DB, total int) {
+ difference := total - GetCaptchaTotal(db)
+
+ for i := 0; i < difference; i++ {
+ CreateNewCaptcha(db)
+ }
+}
+
+func GetFileContentType(out multipart.File) (string, error) {
+
+ buffer := make([]byte, 512)
+
+ _, err := out.Read(buffer)
+ if err != nil {
+ return "", err
+ }
+
+ out.Seek(0, 0)
+
+ contentType := http.DetectContentType(buffer)
+
+ return contentType, nil
+}
+
+func IsReplyInThread(db *sql.DB, inReplyTo string, id string) bool {
+ obj, _ := CheckValidActivity(inReplyTo)
+
+ for _, e := range obj.OrderedItems[0].Replies.OrderedItems {
+ if e.Id == id {
+ return true
+ }
+ }
+ return false
+}
+
+func SupportedMIMEType(mime string) bool {
+ for _, e := range supportedFiles {
+ if e == mime {
+ return true
+ }
+ }
+
+ return false
+}
+
+func DeleteReportActivity(db *sql.DB, id string) bool {
+
+ query := fmt.Sprintf("delete from reported where id='%s'", id)
+
+ _, err := db.Exec(query)
+
+ if err != nil {
+ CheckError(err, "error closing reported activity")
+ return false
+ }
+
+ return true
+}
+
+func ReportActivity(db *sql.DB, id string) bool {
+
+ if !IsIDLocal(db, id) {
+ return false
+ }
+
+ actor := GetActivityFromDB(db, id)
+
+ query := fmt.Sprintf("select count from reported where id='%s'", id)
+
+ rows, err := db.Query(query)
+
+ CheckError(err, "could not select count from reported")
+
+ defer rows.Close()
+ var count int
+ for rows.Next() {
+ rows.Scan(&count)
+ }
+
+ if count < 1 {
+ query = fmt.Sprintf("insert into reported (id, count, board) values ('%s', %d, '%s')", id, 1, actor.Actor)
+
+ _, err := db.Exec(query)
+
+ if err != nil {
+ CheckError(err, "error inserting new reported activity")
+ return false
+ }
+
+ } else {
+ count = count + 1
+ query = fmt.Sprintf("update reported set count=%d where id='%s'", count, id)
+
+ _, err := db.Exec(query)
+
+ if err != nil {
+ CheckError(err, "error updating reported activity")
+ return false
+ }
+ }
+
+ return true
+}
+
+func GetActorReported(w http.ResponseWriter, r *http.Request, db *sql.DB, id string) {
+
+ auth := r.Header.Get("Authorization")
+ verification := strings.Split(auth, " ")
+
+ if len(verification) < 2 {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ return
+ }
+
+ if !HasAuth(db, verification[1], id) {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(""))
+ return
+ }
+
+ var following Collection
+
+ following.AtContext.Context = "https://www.w3.org/ns/activitystreams"
+ following.Type = "Collection"
+ following.TotalItems = GetActorReportedTotal(db, id)
+ following.Items = GetActorReportedDB(db, id)
+
+ enc, _ := json.MarshalIndent(following, "", "\t")
+ w.Header().Set("Content-Type", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"")
+ w.Write(enc)
+}
+
+func MakeActivityRequest(activity Activity) {
+
+ j, _ := json.MarshalIndent(activity, "", "\t")
+ for _, e := range activity.To {
+ actor := GetActor(e)
+
+ req, err := http.NewRequest("POST", actor.Inbox, bytes.NewBuffer(j))
+
+ CheckError(err, "error with sending activity req to")
+
+ _, err = http.DefaultClient.Do(req)
+
+ CheckError(err, "error with sending activity resp to")
+ }
+}
+
+func GetCollectionFromID(id string) Collection {
+ req, err := http.NewRequest("GET", id, nil)
+
+ CheckError(err, "could not get collection from id req")
+
+ resp, err := http.DefaultClient.Do(req)
+
+ CheckError(err, "could not get collection from id resp")
+
+ defer resp.Body.Close()
+
+ body, _ := ioutil.ReadAll(resp.Body)
+
+ var nColl Collection
+
+ 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")
+
+ 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
+}
diff --git a/outboxGet.go b/outboxGet.go
new file mode 100644
index 0000000..55c63f8
--- /dev/null
+++ b/outboxGet.go
@@ -0,0 +1,120 @@
+package main
+
+import "fmt"
+import "net/http"
+import "database/sql"
+import _ "github.com/lib/pq"
+import "encoding/json"
+
+func GetActorOutbox(w http.ResponseWriter, r *http.Request, db *sql.DB) {
+
+ actor := GetActorFromPath(db, r.URL.Path, "/")
+ var collection Collection
+
+ collection.OrderedItems = GetObjectFromDB(db, actor).OrderedItems
+
+ collection.AtContext.Context = "https://www.w3.org/ns/activitystreams"
+ collection.Actor = actor.Id
+
+ collection.TotalItems = GetObjectPostsTotalDB(db, actor)
+ collection.TotalImgs = GetObjectImgsTotalDB(db, actor)
+
+ enc, _ := json.MarshalIndent(collection, "", "\t")
+ w.Header().Set("Content-Type", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"")
+ w.Write(enc)
+}
+
+
+
+func GetObjectsFromFollow(actor Actor) []ObjectBase {
+ var followingCol Collection
+ var followObj []ObjectBase
+ followingCol = GetActorCollection(actor.Following)
+ for _, e := range followingCol.Items {
+ var followOutbox Collection
+ var actor Actor
+ actor = GetActor(e.Id)
+ followOutbox = GetActorCollection(actor.Outbox)
+ for _, e := range followOutbox.OrderedItems {
+ followObj = append(followObj, e)
+ }
+ }
+ return followObj
+}
+
+func GetCollectionFromPath(db *sql.DB, path string) Collection {
+
+ var nColl Collection
+ var result []ObjectBase
+
+ query := fmt.Sprintf("SELECT id, name, content, type, published, attributedto, attachment, actor FROM activitystream WHERE id='%s' ORDER BY published desc;", path)
+
+ rows, err := db.Query(query)
+
+ CheckError(err, "error query collection path from db")
+
+ defer rows.Close()
+
+ for rows.Next(){
+ var actor Actor
+ var post ObjectBase
+ var attachID string
+
+ err = rows.Scan(&post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.AttributedTo, &attachID, &actor.Id)
+
+ CheckError(err, "error scan object into post struct from path")
+
+ post.Actor = &actor
+
+ post.InReplyTo = GetInReplyToDB(db, post)
+
+ post.Replies = GetObjectRepliesDB(db, post)
+
+ post.Replies.TotalItems, post.Replies.TotalImgs = GetObjectRepliesDBCount(db, post)
+
+ post.Attachment = GetObjectAttachment(db, attachID)
+
+ result = append(result, post)
+ }
+
+ nColl.AtContext.Context = "https://www.w3.org/ns/activitystreams"
+
+ nColl.OrderedItems = result
+
+ return nColl
+}
+
+func GetObjectFromPath(db *sql.DB, path string) ObjectBase{
+
+ var nObj ObjectBase
+ var result []ObjectBase
+
+ query := fmt.Sprintf("SELECT id, name, content, type, published, attributedto, attachment, actor FROM activitystream WHERE id='%s' ORDER BY published desc;", path)
+
+ rows, err := db.Query(query)
+
+ CheckError(err, "error query collection path from db")
+
+ defer rows.Close()
+
+ for rows.Next(){
+ var post ObjectBase
+ var attachID string
+
+ err = rows.Scan(&post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.AttributedTo, &attachID, &post.Actor)
+
+ CheckError(err, "error scan object into post struct from path")
+
+ post.Replies = GetObjectRepliesDB(db, post)
+
+ post.Replies.TotalItems, post.Replies.TotalImgs = GetObjectRepliesDBCount(db, post)
+
+ post.Attachment = GetObjectAttachment(db, attachID)
+
+ result = append(result, post)
+ }
+
+ nObj = result[0]
+
+ return nObj
+}
diff --git a/verification.go b/verification.go
new file mode 100644
index 0000000..f233fe6
--- /dev/null
+++ b/verification.go
@@ -0,0 +1,456 @@
+package main
+
+import "fmt"
+import "database/sql"
+import _ "github.com/lib/pq"
+import "net/smtp"
+import "time"
+import "os/exec"
+import "os"
+import "math/rand"
+
+type Verify struct {
+ Type string
+ Identifier string
+ Code string
+ Created string
+ Board string
+}
+
+type VerifyCooldown struct {
+ Identifier string
+ Code string
+ Time int
+}
+
+func DeleteBoardMod(db *sql.DB, verify Verify) {
+ query := fmt.Sprintf("select code from boardaccess where identifier='%s' and board='%s'", verify.Identifier, verify.Board)
+
+ rows, err := db.Query(query)
+
+ CheckError(err, "could not select code from boardaccess")
+
+ defer rows.Close()
+
+ var code string
+ rows.Next()
+ rows.Scan(&code)
+
+ if code != "" {
+ query := fmt.Sprintf("delete from crossverification where code='%s'", code)
+
+
+ _, err := db.Exec(query)
+
+ CheckError(err, "could not delete code from crossverification")
+
+ query = fmt.Sprintf("delete from boardaccess where identifier='%s' and board='%s'", verify.Identifier, verify.Board)
+
+ _, err = db.Exec(query)
+
+ CheckError(err, "could not delete identifier from boardaccess")
+ }
+}
+
+func GetBoardMod(db *sql.DB, identifier string) Verify{
+ var nVerify Verify
+
+ query := fmt.Sprintf("select code, board, type, identifier from boardaccess where identifier='%s'", identifier)
+
+ rows, err := db.Query(query)
+
+ CheckError(err, "could not select boardaccess query")
+
+ defer rows.Close()
+
+ rows.Next()
+ rows.Scan(&nVerify.Code, &nVerify.Board, &nVerify.Type, &nVerify.Identifier)
+
+ return nVerify
+}
+
+func CreateBoardMod(db *sql.DB, verify Verify) {
+ pass := CreateKey(50)
+
+ query := fmt.Sprintf("select code from verification where identifier='%s' and type='%s'", verify.Board, verify.Type)
+
+ rows, err := db.Query(query)
+
+ CheckError(err, "could not select verifcaiton query")
+
+ defer rows.Close()
+
+ var code string
+
+ rows.Next()
+ rows.Scan(&code)
+
+ if code != "" {
+
+ query := fmt.Sprintf("select identifier from boardaccess where identifier='%s' and board='%s'", verify.Identifier, verify.Board)
+
+ rows, err := db.Query(query)
+
+ CheckError(err, "could not select idenifier from boardaccess")
+
+ defer rows.Close()
+
+ var ident string
+ rows.Next()
+ rows.Scan(&ident)
+
+ if ident != verify.Identifier {
+
+ query := fmt.Sprintf("insert into crossverification (verificationcode, code) values ('%s', '%s')", code, pass)
+
+ _, err := db.Exec(query)
+
+ CheckError(err, "could not insert new crossverification")
+
+ query = fmt.Sprintf("insert into boardaccess (identifier, code, board, type) values ('%s', '%s', '%s', '%s')", verify.Identifier, pass, verify.Board, verify.Type)
+
+ _, err = db.Exec(query)
+
+ CheckError(err, "could not insert new boardaccess")
+
+ fmt.Printf("Board access - Board: %s, Identifier: %s, Code: %s\n", verify.Board, verify.Identifier, pass)
+ }
+ }
+}
+
+func CreateVerification(db *sql.DB, verify Verify) {
+ query := fmt.Sprintf("insert into verification (type, identifier, code, created) values ('%s', '%s', '%s', '%s') ", verify.Type, verify.Identifier, verify.Code, time.Now().Format(time.RFC3339))
+
+ _, err := db.Exec(query)
+
+ CheckError(err, "error creating verify")
+}
+
+func GetVerificationByEmail(db *sql.DB, email string) Verify {
+ var verify Verify
+
+ query := fmt.Sprintf("select type, identifier, code, board from boardaccess where identifier='%s';", email)
+
+ rows, err := db.Query(query)
+
+ defer rows.Close()
+
+ CheckError(err, "error getting verify by email query")
+
+ defer rows.Close()
+
+ for rows.Next() {
+ err := rows.Scan(&verify.Type, &verify.Identifier, &verify.Code, &verify.Board)
+
+ CheckError(err, "error getting verify by email scan")
+ }
+
+ return verify
+}
+
+func GetVerificationByCode(db *sql.DB, code string) Verify {
+ var verify Verify
+
+ query := fmt.Sprintf("select type, identifier, code, board from boardaccess where code='%s';", code)
+
+ rows, err := db.Query(query)
+
+ defer rows.Close()
+
+ if err != nil {
+ CheckError(err, "error getting verify by code query")
+ return verify
+ }
+
+ for rows.Next() {
+ err := rows.Scan(&verify.Type, &verify.Identifier, &verify.Code, &verify.Board)
+
+ CheckError(err, "error getting verify by code scan")
+ }
+
+ return verify
+}
+
+func VerifyCooldownCurrent(db *sql.DB, auth string) VerifyCooldown {
+ var current VerifyCooldown
+
+ query := fmt.Sprintf("select identifier, code, time from verificationcooldown where code='%s'", auth)
+
+ rows, err := db.Query(query)
+
+ defer rows.Close()
+
+ if err != nil {
+
+ query := fmt.Sprintf("select identifier, code, time from verificationcooldown where identifier='%s'", auth)
+
+ rows, err := db.Query(query)
+
+ defer rows.Close()
+
+ if err != nil {
+ return current
+ }
+
+ defer rows.Close()
+
+ for rows.Next() {
+ err = rows.Scan(&current.Identifier, &current.Code, &current.Time)
+
+ CheckError(err, "error scanning current verify cooldown verification")
+ }
+ }
+
+ defer rows.Close()
+
+ for rows.Next() {
+ err = rows.Scan(&current.Identifier, &current.Code, &current.Time)
+
+ CheckError(err, "error scanning current verify cooldown code")
+ }
+
+ return current
+}
+
+func VerifyCooldownAdd(db *sql.DB, verify Verify) {
+ query := fmt.Sprintf("insert into verficationcooldown (identifier, code) values ('%s', '%s');", verify.Identifier, verify.Code)
+
+ _, err := db.Exec(query)
+
+ CheckError(err, "error adding verify to cooldown")
+}
+
+func VerficationCooldown(db *sql.DB) {
+
+ query := fmt.Sprintf("select identifier, code, time from verificationcooldown")
+
+ rows, err := db.Query(query)
+
+ defer rows.Close()
+
+ CheckError(err, "error with verifiy cooldown query ")
+
+ defer rows.Close()
+
+ for rows.Next() {
+ var verify VerifyCooldown
+ err = rows.Scan(&verify.Identifier, &verify.Code, &verify.Time)
+
+ CheckError(err, "error with verifiy cooldown scan ")
+
+ nTime := verify.Time - 1;
+
+ query = fmt.Sprintf("update set time='%s' where identifier='%s'", nTime, verify.Identifier)
+
+ _, err := db.Exec(query)
+
+ CheckError(err, "error with update cooldown query")
+
+ VerficationCooldownRemove(db)
+ }
+}
+
+func VerficationCooldownRemove(db *sql.DB) {
+ query := fmt.Sprintf("delete from verificationcooldown where time < 1;")
+
+ _, err := db.Exec(query)
+
+ CheckError(err, "error with verifiy cooldown remove query ")
+}
+
+func SendVerification(verify Verify) {
+
+ fmt.Println("sending email")
+
+ from := SiteEmail
+ pass := SiteEmailPassword
+ to := verify.Identifier
+ body := fmt.Sprintf("You can use either\r\nEmail: %s \r\n Verfication Code: %s\r\n for the board %s", verify.Identifier, verify.Code, verify.Board)
+
+ msg := "From: " + from + "\n" +
+ "To: " + to + "\n" +
+ "Subject: Image Board Verification\n\n" +
+ body
+
+ err := smtp.SendMail(SiteEmailServer + ":" + SiteEmailPort,
+ smtp.PlainAuth("", from, pass, SiteEmailServer),
+ from, []string{to}, []byte(msg))
+
+
+ CheckError(err, "error with smtp")
+}
+
+func IsEmailSetup() bool {
+ if SiteEmail == "" {
+ return false
+ }
+
+ if SiteEmailPassword == "" {
+ return false
+ }
+
+ if SiteEmailServer == "" {
+ return false
+ }
+
+ if SiteEmailPort == "" {
+ return false
+ }
+
+ return true
+}
+
+func HasAuth(db *sql.DB, code string, board string) bool {
+
+ verify := GetVerificationByCode(db, code)
+
+ if HasBoardAccess(db, verify) {
+ return true
+ }
+
+ fmt.Println("has auth is false")
+
+ return false;
+}
+
+func HasAuthCooldown(db *sql.DB, auth string) bool {
+ current := VerifyCooldownCurrent(db, auth)
+ if current.Time > 0 {
+ return true
+ }
+
+ fmt.Println("has auth is false")
+ return false
+}
+
+func GetVerify(db *sql.DB, access string) Verify {
+ verify := GetVerificationByCode(db, access)
+
+ if verify.Identifier == "" {
+ verify = GetVerificationByEmail(db, access)
+ }
+
+ return verify
+}
+
+func CreateNewCaptcha(db *sql.DB){
+ id := RandomID(8)
+ file := "public/" + id + ".png"
+
+ for true {
+ if _, err := os.Stat("./" + file); err == nil {
+ id = RandomID(8)
+ file = "public/" + id + ".png"
+ }else{
+ break
+ }
+ }
+
+ captcha := Captcha()
+
+ var pattern string
+ rnd := fmt.Sprintf("%d", rand.Intn(3))
+
+ srnd := string(rnd)
+
+ switch srnd {
+ case "0" :
+ pattern = "pattern:verticalbricks"
+ break
+
+ case "1" :
+ pattern = "pattern:verticalsaw"
+ break
+
+ case "2" :
+ pattern = "pattern:hs_cross"
+ break
+
+ }
+
+ cmd := exec.Command("convert", "-size", "200x98", pattern, "-transparent", "white", file)
+
+ err := cmd.Run()
+
+ CheckError(err, "error with captcha first pass")
+
+ cmd = exec.Command("convert", file, "-fill", "blue", "-pointsize", "62", "-annotate", "+0+70", captcha, "-tile", "pattern:left30", "-gravity", "center", "-transparent", "white", file)
+
+ err = cmd.Run()
+
+ CheckError(err, "error with captcha second pass")
+
+ rnd = fmt.Sprintf("%d", rand.Intn(24) - 12)
+
+ cmd = exec.Command("convert", file, "-rotate", rnd, "-wave", "5x35", "-distort", "Arc", "20", "-wave", "2x35", "-transparent", "white", file)
+
+ err = cmd.Run()
+
+ CheckError(err, "error with captcha third pass")
+
+ var verification Verify
+ verification.Type = "captcha"
+ verification.Code = captcha
+ verification.Identifier = file
+
+ CreateVerification(db, verification)
+}
+
+func CreateBoardAccess(db *sql.DB, verify Verify) {
+ if(!HasBoardAccess(db, verify)){
+ query := fmt.Sprintf("insert into boardaccess (identifier, board) values('%s', '%s')",
+ verify.Identifier, verify.Board)
+
+ _, err := db.Exec(query)
+
+ CheckError(err, "could not instert verification and board into board access")
+ }
+}
+
+func HasBoardAccess(db *sql.DB, verify Verify) bool {
+ query := fmt.Sprintf("select count(*) from boardaccess where identifier='%s' and board='%s'",
+ verify.Identifier, verify.Board)
+
+ rows, err := db.Query(query)
+
+ defer rows.Close()
+
+ CheckError(err, "could not select boardaccess based on verify")
+
+ var count int
+
+ rows.Next()
+ rows.Scan(&count)
+
+ if(count > 0) {
+ return true
+ } else {
+ return false
+ }
+}
+
+func BoardHasAuthType(db *sql.DB, board string, auth string) bool {
+ authTypes := GetActorAuth(db, board)
+
+ for _, e := range authTypes {
+ if(e == auth){
+ return true
+ }
+ }
+
+ return false
+}
+
+func Captcha() string {
+ rand.Seed(time.Now().UnixNano())
+ domain := "ABEFHKMNPQRSUVWXYZ#$&"
+ rng := 4
+ newID := ""
+ for i := 0; i < rng; i++ {
+ newID += string(domain[rand.Intn(len(domain))])
+ }
+
+ return newID
+}
+
+