package toot import ( "encoding/json" "errors" "fmt" "strings" "git.eeqj.de/sneak/feta/jsonapis" "github.com/rs/zerolog/log" //import "github.com/davecgh/go-spew/spew" //import "encoding/hex" mh "github.com/multiformats/go-multihash" mhopts "github.com/multiformats/go-multihash/opts" ) // Hash is a type for storing a string-based base58 multihash of a // toot's identity // Toot is an object we use internally for storing a discovered toot type Toot struct { Original []byte Parsed *jsonapis.APISerializedToot Hash string FromHost string } // NewTootCollectionFromMastodonAPIResponse takes a byte array from a masto // api response and provides you with a nice array of pointers to parsed // toots func NewTootCollectionFromMastodonAPIResponse(in []byte, hostname string) ([]*Toot, error) { var rt []json.RawMessage err := json.Unmarshal(in, &rt) if err != nil { return nil, errors.New("unable to parse api response") } var tc []*Toot // iterate over rawtoots from api for _, item := range rt { parsed := new(jsonapis.APISerializedToot) err := json.Unmarshal(item, parsed) if err != nil { log.Error().Msg("unable to parse toot, skipping") continue } t := new(Toot) t.Parsed = parsed o, err := item.MarshalJSON() if err != nil { panic(err) } t.Original = o t.FromHost = hostname t.calcHash() tc = append(tc, t) } return tc, nil } func (t *Toot) String() string { return fmt.Sprintf("%#v", t) } func (t *Toot) multiHash(in []byte) string { opts := new(mhopts.Options) opts.Algorithm = "sha2-256" opts.Encoding = "base58" var found bool opts.AlgorithmCode, found = mh.Names[opts.Algorithm] if !found { panic("oops") } opts.Length = mh.DefaultLengths[opts.AlgorithmCode] r := strings.NewReader(string(in)) h, err := opts.Multihash(r) if err != nil { panic(err) } return h.B58String() } // DiskStoragePath is a helper function on a Toot that allows it to provide // a storage path on disk. This should probably be moved into the FSStorage // backend instead. FIXME // It's here because it's a pure function that just formats its own toot attributes // into a string. func (t *Toot) DiskStoragePath() string { // FIXME make this error if fields are missing // '/YYYYMMDD/example.com/username/YYYY-MM-DD.HHMMSS.username@fromHost.multihash.json' return fmt.Sprintf("%s/%s/%s/%s.%s@%s.%s.json", t.Parsed.CreatedAt.Format("20060102"), strings.ToLower(t.FromHost), t.Parsed.Account.Acct, t.Parsed.CreatedAt.Format("2006-01-02.150405"), t.Parsed.Account.Acct, strings.ToLower(t.FromHost), t.Hash, ) } func (t *Toot) identityHashInput() string { return fmt.Sprintf( "%s.%s.%s.%s.%s", t.Parsed.Account.URL, t.Parsed.CreatedAt, t.Parsed.ID, t.Parsed.Content, strings.ToLower(t.FromHost), ) } func (t *Toot) GetHash() string { if t.Hash == "" { t.calcHash() } return t.Hash } func (t *Toot) calcHash() { hi := t.identityHashInput() t.Hash = string(t.multiHash([]byte(hi))) }