Inital commit

It tries to reply to a mastodon post but gets 500
master
Michael Demetriou 2019-09-04 12:33:32 +03:00
commit f67906c96a
8 changed files with 852 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.snip
storage

75
TODO Normal file
View File

@ -0,0 +1,75 @@
[ ] Load outbox of users and parse latest posts
[ ] Write these posts to local file
(normally we would need to sort by time but this is a
temporary solution until we really follow the actors
and get notifications in timely manner)
[ ] Follow users
[ ] Announcements (read up how boost json looks like)
[ ] Federate the post to our followers (hardcoded for now)
[ ] Actor should have pubActor not the other way around
[ ] Handle more than one local actors
[ ] Fix the json to host those multiple actors
[ ] Fix the json unmarshalling code to read multiple actors
[ ] Handle the /actor endpoint
[ ] Create configuration file
[ ] Implement database backend
[ ] Create a file with the actors we have, their following
and their followers.
[ ] `MakeActor` should create a file with that actor.
[ ] Implement `LoadActor`
[ ] All but `main.go` should run LoadActor instead of MakeActor
(Actually nobody should run LoadActor except GetActor)
[ ] `actor.Follow` should write the new following to file
[ ] Handle being followed
[ ] When followed, the handler should write the new follower to file
[ ] Make sure we send our boosts to all our followers
Code is there but it works sometimes (I hate when this happens)
[ ] Check why I get calls to get with an id that consists only of an actor's name
[ ] Implement `db.followers` and `db.following`
[ ] Write all the announcements (boosts) to the database to
their correct actors
[ ] Check if we are already following users
[ ] On GetOutbox read the database and present a list of the
last posts.
[ ] Make OS-independent (mosty directory separators)
[ ] Create outbox.json programmatically
[ ] Make storage configurable (search for "storage" in project)
[ ] Check if we're boosting only stuff from actors we follow, not whatever comes
through in our inbox
[ ] Boost not only articles but other things too
[ ] Handle post uri's
[ ] Sanitize input, never allow slashes or dots
[ ] Add summary to actors.json
[ ] Check local actor names for characters illegal for filenames and ban them
[ ] Create debug flag
[ ] Write to following only upon accept
(waiting to actually get an accept so that I can test this)
[ ] Implement webfinger
[ ] Make sure masto finds signature
[ ] Implement Unfollow
[ ] Implement accept (accept when other follow us)
(done but can't test it pending http signatures)
Works in pleroma/pixelfed not working on masto
(nothing works on masto)
[ ] Implement nodeinfo and statistics
[ ] Accept even if already follows us
[ ] Implement db.Update
[ ] Implement db.Delete
[ ] Handle paging
[ ] Handle http signatures
masto can't find the signature
[ ] Verify http signatures
[ ] Why doesn't our outbox being fetched by others?
[ ] Refactor, comment and clean up
[ ] Make sure we never show <actor>.json to the public
[ ] Split to pherephone and activityServe
[ ] Decide what's to be done with actors removed from `actors.json`.
[ ] Remove them?
[ ] Leave them read-only?
[ ] Leave them as is?
[ ] Check if an early failure in announcing posts causes a problem to the following ones
[ ] Handle followers and following uri's
[ ] Do I care about the inbox?
[ ] Maybe look at implementing lock files?
[ ] Check if it's worth it to reuse pubActor instead of creating
a new one every time

435
activityserve/actor.go Normal file
View File

@ -0,0 +1,435 @@
package activityserve
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"strings"
"time"
"github.com/gologme/log"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"github.com/dchest/uniuri"
"github.com/go-fed/httpsig"
)
// Actor represents a local actor we can act on
// behalf of.
type Actor struct {
name, summary, actorType, iri string
followersIRI string
nuIri *url.URL
followers, following map[string]interface{}
posts map[int]map[string]interface{}
publicKey crypto.PublicKey
privateKey crypto.PrivateKey
publicKeyPem string
privateKeyPem string
publicKeyID string
}
// ActorToSave is a stripped down actor representation
// with exported properties in order for json to be
// able to marshal it.
// see https://stackoverflow.com/questions/26327391/json-marshalstruct-returns
type ActorToSave struct {
Name, Summary, ActorType, IRI, PublicKey, PrivateKey string
Followers, Following map[string]interface{}
}
// MakeActor returns a new local actor we can act
// on behalf of
func MakeActor(name, summary, actorType string) (Actor, error) {
followers := make(map[string]interface{})
following := make(map[string]interface{})
followersIRI := baseURL + name + "/followers"
publicKeyID := baseURL + name + "#main-key"
iri := baseURL + "/" + name
nuIri, err := url.Parse(iri)
if err != nil {
log.Info("Something went wrong when parsing the local actor uri into net/url")
return Actor{}, err
}
actor := Actor{
name: name,
summary: summary,
actorType: actorType,
iri: iri,
nuIri: nuIri,
followers: followers,
following: following,
followersIRI: followersIRI,
publicKeyID: publicKeyID,
}
// create actor's keypair
rng := rand.Reader
privateKey, err := rsa.GenerateKey(rng, 2048)
publicKey := privateKey.PublicKey
actor.publicKey = publicKey
actor.privateKey = privateKey
// marshal the crypto to pem
privateKeyDer := x509.MarshalPKCS1PrivateKey(privateKey)
privateKeyBlock := pem.Block{
Type: "RSA PRIVATE KEY",
Headers: nil,
Bytes: privateKeyDer,
}
actor.privateKeyPem = string(pem.EncodeToMemory(&privateKeyBlock))
publicKeyDer, err := x509.MarshalPKIXPublicKey(&publicKey)
if err != nil {
log.Info("Can't marshal public key")
return Actor{}, err
}
publicKeyBlock := pem.Block{
Type: "PUBLIC KEY",
Headers: nil,
Bytes: publicKeyDer,
}
actor.publicKeyPem = string(pem.EncodeToMemory(&publicKeyBlock))
err = actor.save()
if err != nil {
return actor, err
}
return actor, nil
}
// GetOutboxIRI returns the outbox iri in net/url
func (a *Actor) GetOutboxIRI() *url.URL {
iri := a.iri + "/outbox"
nuiri, _ := url.Parse(iri)
return nuiri
}
// LoadActor searches the filesystem and creates an Actor
// from the data in name.json
func LoadActor(name string) (Actor, error) {
// make sure our users can't read our hard drive
if strings.ContainsAny(name, "./ ") {
log.Info("Illegal characters in actor name")
return Actor{}, errors.New("Illegal characters in actor name")
}
jsonFile := storage + slash + "actors" + slash + name + slash + name + ".json"
fileHandle, err := os.Open(jsonFile)
if os.IsNotExist(err) {
log.Info("We don't have this kind of actor stored")
return Actor{}, err
}
byteValue, err := ioutil.ReadAll(fileHandle)
if err != nil {
log.Info("Error reading actor file")
return Actor{}, err
}
jsonData := make(map[string]interface{})
json.Unmarshal(byteValue, &jsonData)
nuIri, err := url.Parse(jsonData["IRI"].(string))
if err != nil {
log.Info("Something went wrong when parsing the local actor uri into net/url")
return Actor{}, err
}
// publicKeyNewLines := strings.ReplaceAll(jsonData["PublicKey"].(string), "\\n", "\n")
// privateKeyNewLines := strings.ReplaceAll(jsonData["PrivateKey"].(string), "\\n", "\n")
publicKeyDecoded, rest := pem.Decode([]byte(jsonData["PublicKey"].(string)))
if publicKeyDecoded == nil {
log.Info(rest)
panic("failed to parse PEM block containing the public key")
}
publicKey, err := x509.ParsePKIXPublicKey(publicKeyDecoded.Bytes)
if err != nil {
log.Info("Can't parse public keys")
log.Info(err)
return Actor{}, err
}
privateKeyDecoded, rest := pem.Decode([]byte(jsonData["PrivateKey"].(string)))
if privateKeyDecoded == nil {
log.Info(rest)
panic("failed to parse PEM block containing the private key")
}
privateKey, err := x509.ParsePKCS1PrivateKey(privateKeyDecoded.Bytes)
if err != nil {
log.Info("Can't parse private keys")
log.Info(err)
return Actor{}, err
}
actor := Actor{
name: name,
summary: jsonData["Summary"].(string),
actorType: jsonData["ActorType"].(string),
iri: jsonData["IRI"].(string),
nuIri: nuIri,
followers: jsonData["Followers"].(map[string]interface{}),
following: jsonData["Following"].(map[string]interface{}),
publicKey: publicKey,
privateKey: privateKey,
publicKeyPem: jsonData["PublicKey"].(string),
privateKeyPem: jsonData["PrivateKey"].(string),
followersIRI: baseURL + name + "/followers",
publicKeyID: baseURL + name + "#main-key",
}
return actor, nil
}
// save the actor to file
func (a *Actor) save() error {
// check if we already have a directory to save actors
// and if not, create it
dir := storage + slash + "actors" + slash + a.name + slash + "items"
if _, err := os.Stat(dir); os.IsNotExist(err) {
os.MkdirAll(dir, 0755)
}
actorToSave := ActorToSave{
Name: a.name,
Summary: a.summary,
ActorType: a.actorType,
IRI: a.iri,
Followers: a.followers,
Following: a.following,
PublicKey: a.publicKeyPem,
PrivateKey: a.privateKeyPem,
}
actorJSON, err := json.MarshalIndent(actorToSave, "", "\t")
if err != nil {
log.Info("error Marshalling actor json")
return err
}
// log.Info(actorToSave)
// log.Info(string(actorJSON))
err = ioutil.WriteFile(storage+slash+"actors"+slash+a.name+slash+a.name+".json", actorJSON, 0644)
if err != nil {
log.Printf("WriteFileJson ERROR: %+v", err)
return err
}
return nil
}
func (a *Actor) whoAmI() string {
return `{"@context": "https://www.w3.org/ns/activitystreams",
"type": "` + a.actorType + `",
"id": "` + baseURL + a.name + `",
"name": "` + a.name + `",
"preferredUsername": "` + a.name + `",
"summary": "` + a.summary + `",
"inbox": "` + baseURL + a.name + `/inbox",
"outbox": "` + baseURL + a.name + `/outbox",
"followers": "` + baseURL + a.name + `/followers",
"following": "` + baseURL + a.name + `/following",
"liked": "` + baseURL + a.name + `/liked",
"publicKey": {
"id": "` + baseURL + a.name + `#main-key",
"owner": "` + baseURL + a.name + `",
"publicKeyPem": "` + strings.ReplaceAll(a.publicKeyPem, "\n", "\\n") + `"
}
}`
}
func (a *Actor) newID() string {
return uniuri.New()
}
// CreateNote posts an activityPub note to our followers
func (a *Actor) CreateNote(content string) {
// for now I will just write this to the outbox
id := a.newID()
create := make(map[string]interface{})
note := make(map[string]interface{})
context := make([]string, 1)
context[0] = "https://www.w3.org/ns/activitystreams"
create["@context"] = context
create["actor"] = baseURL + a.name
create["cc"] = a.followersIRI
create["id"] = baseURL + a.name + "/" + id
create["object"] = note
note["attributedTo"] = baseURL + a.name
note["cc"] = a.followersIRI
note["content"] = content
note["inReplyTo"] = "https://cybre.space/@qwazix/102688373602724023"
note["id"] = baseURL + a.name + "/note/" + id
note["published"] = time.Now().Format(time.RFC3339)
note["url"] = create["id"]
note["type"] = "Note"
note["to"] = "https://www.w3.org/ns/activitystreams#Public"
create["published"] = note["published"]
create["type"] = "Create"
// note := `{
// "actor" : "https://` + baseURL + a.name + `",
// "cc" : [
// "https://` + baseURL + a.name + `/followers"
// ],
// "id" : "https://` + baseURL + a.name + `/` + id +`",
// "object" : {
// "attributedTo" : "https://` + baseURL + a.name + `",
// "cc" : [
// "https://` + baseURL + a.name + `/followers"
// ],
// "content" : "`+ content + `",
// "id" : "https://` + baseURL + a.name + `/` + id +`",
// "inReplyTo" : null,
// "published" : "2019-08-26T16:25:26Z",
// "to" : [
// "https://www.w3.org/ns/activitystreams#Public"
// ],
// "type" : "Note",
// "url" : "https://` + baseURL + a.name + `/` + id +`"
// },
// "published" : "2019-08-26T16:25:26Z",
// "to" : [
// "https://www.w3.org/ns/activitystreams#Public"
// ],
// "type" : "Create"
// }`
to, _ := url.Parse("https://cybre.space/inbox")
go a.send(create, to)
a.saveItem(id, create)
}
func (a *Actor) saveItem(id string, content map[string]interface{}) error {
JSON, _ := json.MarshalIndent(content, "", "\t")
dir := storage + slash + "actors" + slash + a.name + slash + "items"
err := ioutil.WriteFile(dir+slash+id+".json", JSON, 0644)
if err != nil {
log.Printf("WriteFileJson ERROR: %+v", err)
return err
}
return nil
}
// send is here for backward compatibility and maybe extra pre-processing
// not always required
func (a *Actor) send(content map[string]interface{}, to *url.URL) (err error) {
return a.signedHTTPPost(content, to.String())
}
func (a *Actor) signedHTTPPost(content map[string]interface{}, to string) (err error) {
b, err := json.Marshal(content)
if err != nil {
log.Info("Can't marshal JSON")
log.Info(err)
return
}
postSigner, _, _ := httpsig.NewSigner([]httpsig.Algorithm{httpsig.RSA_SHA256}, []string{"(request-target)", "date", "host", "digest"}, httpsig.Signature)
byteCopy := make([]byte, len(b))
copy(byteCopy, b)
buf := bytes.NewBuffer(byteCopy)
req, err := http.NewRequest("POST", to, buf)
if err != nil {
log.Info(err)
return
}
// I prefer to deal with strings and just parse to net/url if and when
// needed, even if here we do one extra round trip
iri, err := url.Parse(to)
if err != nil {
log.Error("cannot parse url for POST, check your syntax")
return err
}
req.Header.Add("Accept-Charset", "utf-8")
req.Header.Add("Date", time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05")+" GMT")
req.Header.Add("User-Agent", fmt.Sprintf("activityserve 0.0"))
req.Header.Add("Host", iri.Host)
req.Header.Add("Accept", "application/activity+json")
sum := sha256.Sum256(b)
req.Header.Add("Digest",
fmt.Sprintf("SHA-256=%s",
base64.StdEncoding.EncodeToString(sum[:])))
err = postSigner.SignRequest(a.privateKey, a.publicKeyID, req)
if err != nil {
log.Info(err)
return
}
resp, err := client.Do(req)
if err != nil {
log.Info(err)
return
}
defer resp.Body.Close()
if !isSuccess(resp.StatusCode) {
responseData, _ := ioutil.ReadAll(resp.Body)
err = fmt.Errorf("POST request to %s failed (%d): %s\nResponse: %s \nRequest: %s \nHeaders: %s", to, resp.StatusCode, resp.Status, formatJSON(responseData), formatJSON(byteCopy), req.Header)
log.Info(err)
return
}
responseData, _ := ioutil.ReadAll(resp.Body)
fmt.Printf("POST request to %s succeeded (%d): %s \nResponse: %s \nRequest: %s \nHeaders: %s", to, resp.StatusCode, resp.Status, formatJSON(responseData), formatJSON(byteCopy), req.Header)
return
}
func (a *Actor) signedHTTPGet(address string) (string, error){
req, err := http.NewRequest("GET", address, nil)
if err != nil {
log.Error("cannot create new http.request")
return "", err
}
iri, err := url.Parse(address)
if err != nil {
log.Error("cannot parse url for GET, check your syntax")
return "", err
}
req.Header.Add("Accept-Charset", "utf-8")
req.Header.Add("Date", time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05")+" GMT")
req.Header.Add("User-Agent", fmt.Sprintf("%s %s %s", userAgent, libName, version))
req.Header.Add("host", iri.Host)
req.Header.Add("digest", "")
req.Header.Add("Accept", "application/activity+json; profile=\"https://www.w3.org/ns/activitystreams\"")
// set up the http signer
signer, _, _ := httpsig.NewSigner([]httpsig.Algorithm{httpsig.RSA_SHA256}, []string{"(request-target)", "date", "host", "digest"}, httpsig.Signature)
err = signer.SignRequest(a.privateKey, a.publicKeyID, req)
if err != nil {
log.Error("Can't sign the request")
return "", err
}
resp, err := client.Do(req)
if err != nil {
log.Error("Cannot perform the GET request")
log.Error(err)
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
responseData, _ := ioutil.ReadAll(resp.Body)
return "", fmt.Errorf("GET request to %s failed (%d): %s \n%s", iri.String(), resp.StatusCode, resp.Status, formatJSON(responseData))
}
responseData, _ := ioutil.ReadAll(resp.Body)
fmt.Println("GET request succeeded:", iri.String(), req.Header, resp.StatusCode, resp.Status, "\n", formatJSON(responseData))
responseText := string(responseData)
return responseText, nil
}

153
activityserve/http.go Normal file
View File

@ -0,0 +1,153 @@
package activityserve
import (
"fmt"
"net/http"
"strings"
"github.com/gologme/log"
"github.com/gorilla/mux"
"encoding/json"
)
// SetupHTTP starts an http server with all the required handlers
func Serve() {
var webfingerHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("content-type", "application/jrd+json; charset=utf-8")
account := r.URL.Query().Get("resource") // should be something like acct:user@example.com
account = strings.Replace(account, "acct:", "", 1) // remove acct:
server := strings.Split(baseURL, "://")[1] // remove protocol from baseURL. Should get example.com
server = strings.TrimSuffix(server, "/") // remove protocol from baseURL. Should get example.com
account = strings.Replace(account, "@"+server, "", 1) // remove server from handle. Should get user
actor, err := LoadActor(account)
// error out if this actor does not exist
if err != nil {
log.Info("No such actor")
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "404 - actor not found")
return
}
// response := `{"subject":"acct:` + actor.name + `@` + server + `","aliases":["` + baseURL + actor.name + `","` + baseURL + actor.name + `"],"links":[{"href":"` + baseURL + `","type":"text/html","rel":"https://webfinger.net/rel/profile-page"},{"href":"` + baseURL + actor.name + `","type":"application/activity+json","rel":"self"}]}`
responseMap := make(map[string]interface{})
responseMap["subject"] = "acct:" + actor.name + "@" + server
links := make(map[string]string)
links["rel"] = "self"
links["type"] = "application/activity+json"
links["href"] = baseURL + actor.name
responseMap["links"] = links
response, err := json.Marshal(responseMap)
if err != nil {
log.Error("problem creating the webfinger response json")
}
log.Info(string(response))
w.Write([]byte(response))
}
var actorHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("content-type", "application/activity+json; charset=utf-8")
log.Info("Remote server just fetched our /actor endpoint")
username := mux.Vars(r)["actor"]
log.Info(username)
if username == ".well-known" || username == "favicon.ico" {
log.Info("well-known, skipping...")
return
}
actor, err := LoadActor(username)
// error out if this actor does not exist (or there are dots or slashes in his name)
if err != nil {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "404 - page not found")
log.Info("Can't create local actor")
return
}
fmt.Fprintf(w, actor.whoAmI())
log.Info(r.RemoteAddr)
log.Info(r.Body)
log.Info(r.Header)
}
var outboxHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("content-type", "application/activity+json; charset=utf-8")
username := mux.Vars(r)["actor"] // get the needed actor from the muxer (url variable {actor} below)
actor, err := LoadActor(username) // load the actor from disk
if err != nil { // either actor requested has illegal characters or
log.Info("Can't load local actor") // we don't have such actor
fmt.Fprintf(w, "404 - page not found")
w.WriteHeader(http.StatusNotFound)
return
}
var response string
if r.URL.Query().Get("page") == "" {
//TODO fix total items
response = `{
"@context" : "https://www.w3.org/ns/activitystreams",
"first" : "` + baseURL + actor.name + `/outbox?page=true",
"id" : "` + baseURL + actor.name + `/outbox",
"last" : "` + baseURL + actor.name + `/outbox?min_id=0&page=true",
"totalItems" : 10,
"type" : "OrderedCollection"
}`
} else {
content := "Hello, World!"
id := "asfdasdf"
response = `
{
"@context" : "https://www.w3.org/ns/activitystreams",
"id" : "` + baseURL + actor.name + `/outbox?min_id=0&page=true",
"next" : "` + baseURL + actor.name + `/outbox?max_id=99524642494530460&page=true",
"orderedItems" :[
{
"actor" : "https://` + baseURL + actor.name + `",
"cc" : [
"https://` + baseURL + actor.name + `/followers"
],
"id" : "https://` + baseURL + actor.name + `/` + id + `",
"object" : {
"attributedTo" : "https://` + baseURL + actor.name + `",
"cc" : [
"https://` + baseURL + actor.name + `/followers"
],
"content" : "` + content + `",
"id" : "https://` + baseURL + actor.name + `/` + id + `",
"inReplyTo" : null,
"published" : "2019-08-26T16:25:26Z",
"to" : [
"https://www.w3.org/ns/activitystreams#Public"
],
"type" : "Note",
"url" : "https://` + baseURL + actor.name + `/` + id + `"
},
"published" : "2019-08-26T16:25:26Z",
"to" : [
"https://www.w3.org/ns/activitystreams#Public"
],
"type" : "Create"
}
],
"partOf" : "` + baseURL + actor.name + `/outbox",
"prev" : "` + baseURL + actor.name + `/outbox?min_id=99982453036184436&page=true",
"type" : "OrderedCollectionPage"
}`
}
w.Write([]byte(response))
}
// Add the handlers to a HTTP server
gorilla := mux.NewRouter()
gorilla.HandleFunc("/.well-known/webfinger", webfingerHandler)
gorilla.HandleFunc("/{actor}/outbox", outboxHandler)
gorilla.HandleFunc("/{actor}/outbox/", outboxHandler)
// gorilla.HandleFunc("/{actor}/inbox", inboxHandler)
// gorilla.HandleFunc("/{actor}/inbox/", inboxHandler)
gorilla.HandleFunc("/{actor}/", actorHandler)
gorilla.HandleFunc("/{actor}", actorHandler)
// gorilla.HandleFunc("/{actor}/post/{hash}", postHandler)
http.Handle("/", gorilla)
log.Fatal(http.ListenAndServe(":8081", nil))
}

82
activityserve/setup.go Normal file
View File

@ -0,0 +1,82 @@
package activityserve
import (
"fmt"
"os"
"net/http"
"github.com/gologme/log"
"gopkg.in/ini.v1"
)
var slash = string(os.PathSeparator)
var baseURL = "http://example.com/"
var storage = "storage"
var userAgent = "activityserve"
const libName = "activityserve"
const version = "0.99"
var client = http.Client{}
// Setup sets our environment up
func Setup(configurationFile string, debug bool) {
// read configuration file (config.ini)
if configurationFile == "" {
configurationFile = "config.ini"
}
cfg, err := ini.Load("config.ini")
if err != nil {
fmt.Printf("Fail to read file: %v", err)
os.Exit(1)
}
// Load base url from configuration file
baseURL = cfg.Section("general").Key("baseURL").String()
// check if it ends with a / and append one if not
if baseURL[len(baseURL)-1:] != "/" {
baseURL += "/"
}
// print it for our users
fmt.Println()
fmt.Println("Domain Name:", baseURL)
// Load storage location (only local filesystem supported for now) from config
storage = cfg.Section("general").Key("storage").String()
cwd, err := os.Getwd()
fmt.Println("Storage Location:", cwd+slash+storage)
fmt.Println()
SetupStorage(storage)
// Load user agent
userAgent = cfg.Section("general").Key("userAgent").String()
// I prefer long file so that I can click it in the terminal and open it
// in the editor above
log.SetFlags(log.Llongfile)
// log.SetFlags(log.LstdFlags | log.Lshortfile)
log.EnableLevel("warn")
// create a logger with levels but without prefixes for easier to read
// debug output
printer := log.New(os.Stdout, " ", 0)
if debug == true {
fmt.Println()
fmt.Println("debug mode on")
log.EnableLevel("info")
printer.EnableLevel("info")
}
}
// SetupStorage creates storage
func SetupStorage(storage string) {
// prepare storage for foreign activities (activities we store that don't
// belong to us)
foreignDir := storage + slash + "foreign"
if _, err := os.Stat(foreignDir); os.IsNotExist(err) {
os.MkdirAll(foreignDir, 0755)
}
}

41
activityserve/util.go Normal file
View File

@ -0,0 +1,41 @@
package activityserve
import (
"net/http"
// "net/url"
"bytes"
"encoding/json"
// "time"
// "fmt"
"github.com/gologme/log"
// "github.com/go-fed/httpsig"
)
func isSuccess(code int) bool {
return code == http.StatusOK ||
code == http.StatusCreated ||
code == http.StatusAccepted
}
//PrettyPrint maps
func PrettyPrint(themap map[string]interface{}) {
b, err := json.MarshalIndent(themap, "", " ")
if err != nil {
log.Info("error:", err)
}
log.Print(string(b))
}
//PrettyPrintJSON does what it's name says
func PrettyPrintJSON(theJSON []byte) {
dst := new(bytes.Buffer)
json.Indent(dst, theJSON, "", "\t")
log.Info(dst)
}
func formatJSON(theJSON []byte) string{
dst := new(bytes.Buffer)
json.Indent(dst, theJSON, "", "\t")
return dst.String()
}

5
config.ini Normal file
View File

@ -0,0 +1,5 @@
[general]
baseURL = https://floorb.qwazix.com
storage = storage ; can be relative or absolute path
userAgent = "pherephone"

59
main.go Normal file
View File

@ -0,0 +1,59 @@
package main
import (
"fmt"
"flag"
// "os"
// "strings"
// "errors"
// "encoding/json"
// "io/ioutil"
// "net/http"
// "net/url"
// "context"
// "html"
"github.com/gologme/log"
// "github.com/go-fed/activity/streams"
// "github.com/gorilla/mux"
// "gopkg.in/ini.v1"
// "github.com/davecgh/go-spew/spew"
"./activityserve"
)
var err error
func main() {
// This is here for debugging purposes. I want to be able to easily spot in the terminal
// when a single execution starts
fmt.Println()
fmt.Println("======================= PHeRePHoNe ==========================")
// introduce ourselves
fmt.Println()
fmt.Println("Pherephone follows some accounts and boosts")
fmt.Println("whatever they post to our followers. See config.ini ")
fmt.Println("for more information and how to set up. ")
debugFlag := flag.Bool("debug", false, "set to true to get debugging information in the console")
flag.Parse()
if *debugFlag == true {
log.EnableLevel("info")
}
activityserve.Setup("config.ini", *debugFlag)
actor, _ := activityserve.MakeActor("activityserve_test_actor_2", "This is an activityserve test actor", "Service")
// actor, _ := activityserve.LoadActor("activityserve_test_actor_2")
actor.CreateNote("Hello World!")
activityserve.Serve()
}