matching import for the honk export. roughly roundtrips now.

This commit is contained in:
Ted Unangst 2023-08-18 20:53:42 -04:00
parent 842816f1bf
commit 20699112a0
3 changed files with 71 additions and 21 deletions

View File

@ -194,10 +194,13 @@ and templates are reloaded every request.
Data may be imported and converted from other services using the Data may be imported and converted from other services using the
.Ic import .Ic import
command. command.
Currently supports Mastodon, Twitter, and Instagram exported data. Currently supports Honk, Mastodon, Twitter, and Instagram exported data.
Posts are imported and backdated to appear as old honks. Posts are imported and backdated to appear as old honks.
The Mastodon following list is imported, but must be refollowed. The Mastodon following list is imported, but must be refollowed.
.Pp .Pp
To prepare a Honk data archive, extract the export.zip file.
.Dl ./honk import username honk source-directory
.Pp
To prepare a Mastodon data archive, extract the archive-longhash.tar.gz file. To prepare a Mastodon data archive, extract the archive-longhash.tar.gz file.
.Dl ./honk import username mastodon source-directory .Dl ./honk import username mastodon source-directory
.Pp .Pp
@ -208,6 +211,13 @@ and unzip any zip files contained within.
.Pp .Pp
To prepare an Instagram data archive, extract the igusername.zip file. To prepare an Instagram data archive, extract the igusername.zip file.
.Dl ./honk import username instagram source-directory .Dl ./honk import username instagram source-directory
.Ss Export
User data may be exported to a zip archive using the
.Ic export
command.
This will export the user's outbox and inbox in ActvityPub json format,
along with associated media.
.Dl ./honk export username zipname
.Ss Advanced Options .Ss Advanced Options
Advanced configuration values may be set by running the Advanced configuration values may be set by running the
.Ic setconfig Ar key value .Ic setconfig Ar key value

View File

@ -36,6 +36,8 @@ func importMain(username, flavor, source string) {
switch flavor { switch flavor {
case "mastodon": case "mastodon":
importMastodon(username, source) importMastodon(username, source)
case "honk":
importHonk(username, source)
case "twitter": case "twitter":
importTwitter(username, source) importTwitter(username, source)
case "instagram": case "instagram":
@ -45,11 +47,13 @@ func importMain(username, flavor, source string) {
} }
} }
type TootObject struct { type ActivityObject struct {
AttributedTo string
Summary string Summary string
Content string Content string
InReplyTo string InReplyTo string
Conversation string Conversation string
Context string
Published time.Time Published time.Time
Tag []struct { Tag []struct {
Type string Type string
@ -63,10 +67,10 @@ type TootObject struct {
} }
} }
type PlainTootObject TootObject type PlainActivityObject ActivityObject
func (obj *TootObject) UnmarshalJSON(b []byte) error { func (obj *ActivityObject) UnmarshalJSON(b []byte) error {
p := (*PlainTootObject)(obj) p := (*PlainActivityObject)(obj)
json.Unmarshal(b, p) json.Unmarshal(b, p)
return nil return nil
} }
@ -77,8 +81,9 @@ func importMastodon(username, source string) {
elog.Fatal(err) elog.Fatal(err)
} }
if _, err := os.Stat(source + "/outbox.json"); err == nil { outbox := source + "/outbox.json"
importMastotoots(user, source) if _, err := os.Stat(outbox); err == nil {
importActivities(user, outbox, source)
} else { } else {
ilog.Printf("skipping outbox.json!") ilog.Printf("skipping outbox.json!")
} }
@ -89,19 +94,33 @@ func importMastodon(username, source string) {
} }
} }
func importMastotoots(user *WhatAbout, source string) { func importHonk(username, source string) {
type Toot struct { user, err := butwhatabout(username)
if err != nil {
elog.Fatal(err)
}
outbox := source + "/outbox.json"
if _, err := os.Stat(outbox); err == nil {
importActivities(user, outbox, source)
} else {
ilog.Printf("skipping outbox.json!")
}
}
func importActivities(user *WhatAbout, filename, source string) {
type Activity struct {
Id string Id string
Type string Type string
To []string To interface{}
Cc []string Cc []string
Object TootObject Object ActivityObject
} }
var outbox struct { var outbox struct {
OrderedItems []Toot OrderedItems []Activity
} }
ilog.Println("Importing honks...") ilog.Println("Importing honks...")
fd, err := os.Open(source + "/outbox.json") fd, err := os.Open(filename)
if err != nil { if err != nil {
elog.Fatal(err) elog.Fatal(err)
} }
@ -123,7 +142,11 @@ func importMastotoots(user *WhatAbout, source string) {
} }
re_tootid := regexp.MustCompile("[^/]+$") re_tootid := regexp.MustCompile("[^/]+$")
for _, item := range outbox.OrderedItems { items := outbox.OrderedItems
for i, j := 0, len(items)-1; i < j; i, j = i+1, j-1 {
items[i], items[j] = items[j], items[i]
}
for _, item := range items {
toot := item toot := item
if toot.Type != "Create" { if toot.Type != "Create" {
continue continue
@ -136,6 +159,21 @@ func importMastotoots(user *WhatAbout, source string) {
if havetoot(xid) { if havetoot(xid) {
continue continue
} }
convoy := toot.Object.Context
if convoy == "" {
convoy = toot.Object.Conversation
}
var audience []string
to, ok := toot.To.(string)
if ok {
audience = append(audience, to)
} else {
for _, t := range toot.To.([]interface{}) {
audience = append(audience, t.(string))
}
}
audience = append(audience, toot.Cc...)
honk := Honk{ honk := Honk{
UserID: user.ID, UserID: user.ID,
What: "honk", What: "honk",
@ -144,9 +182,9 @@ func importMastotoots(user *WhatAbout, source string) {
RID: toot.Object.InReplyTo, RID: toot.Object.InReplyTo,
Date: toot.Object.Published, Date: toot.Object.Published,
URL: xid, URL: xid,
Audience: append(toot.To, toot.Cc...), Audience: audience,
Noise: toot.Object.Content, Noise: toot.Object.Content,
Convoy: toot.Object.Conversation, Convoy: convoy,
Whofore: 2, Whofore: 2,
Format: "html", Format: "html",
Precis: toot.Object.Summary, Precis: toot.Object.Summary,
@ -537,14 +575,15 @@ func export(username, file string) {
rows, err := stmtUserHonks.Query(0, 3, user.Name, "0", 1234567) rows, err := stmtUserHonks.Query(0, 3, user.Name, "0", 1234567)
honks := getsomehonks(rows, err) honks := getsomehonks(rows, err)
for _, honk := range honks { for _, honk := range honks {
for _, donk := range honk.Donks {
donk.URL = "media/" + donk.XID
donks[donk.XID] = true
}
noise := honk.Noise noise := honk.Noise
j, jo := jonkjonk(user, honk) j, jo := jonkjonk(user, honk)
if honk.Format == "markdown" { if honk.Format == "markdown" {
jo["source"] = noise jo["source"] = noise
} }
for _, donk := range honk.Donks {
donks[donk.XID] = true
}
jonks = append(jonks, j) jonks = append(jonks, j)
} }
j := junk.New() j := junk.New()
@ -565,10 +604,11 @@ func export(username, file string) {
rows, err := stmtHonksForMe.Query(0, user.ID, "0", user.ID, 1234567) rows, err := stmtHonksForMe.Query(0, user.ID, "0", user.ID, 1234567)
honks := getsomehonks(rows, err) honks := getsomehonks(rows, err)
for _, honk := range honks { for _, honk := range honks {
j, _ := jonkjonk(user, honk)
for _, donk := range honk.Donks { for _, donk := range honk.Donks {
donk.URL = "media/" + donk.XID
donks[donk.XID] = true donks[donk.XID] = true
} }
j, _ := jonkjonk(user, honk)
jonks = append(jonks, j) jonks = append(jonks, j)
} }
j := junk.New() j := junk.New()

View File

@ -119,7 +119,7 @@ func main() {
adminscreen() adminscreen()
case "import": case "import":
if len(args) != 4 { if len(args) != 4 {
elog.Fatal("import username mastodon|twitter srcdir") elog.Fatal("import username honk|mastodon|twitter srcdir")
} }
importMain(args[1], args[2], args[3]) importMain(args[1], args[2], args[3])
case "export": case "export":