all repos — honk @ 0a68a79e79989a30376953a93cabbbba508653af

my fork of honk

import.go (view raw)

  1//
  2// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
  3//
  4// Permission to use, copy, modify, and distribute this software for any
  5// purpose with or without fee is hereby granted, provided that the above
  6// copyright notice and this permission notice appear in all copies.
  7//
  8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 15
 16package main
 17
 18import (
 19	"encoding/json"
 20	"fmt"
 21	"html"
 22	"io/ioutil"
 23	"log"
 24	"os"
 25	"regexp"
 26	"sort"
 27	"strings"
 28	"time"
 29)
 30
 31func importMain(username, flavor, source string) {
 32	switch flavor {
 33	case "mastodon":
 34		importMastodon(username, source)
 35	case "twitter":
 36		importTwitter(username, source)
 37	default:
 38		log.Fatal("unknown source flavor")
 39	}
 40}
 41
 42func importMastodon(username, source string) {
 43	user, err := butwhatabout(username)
 44	if err != nil {
 45		log.Fatal(err)
 46	}
 47	type Toot struct {
 48		Id           string
 49		Type         string
 50		To           []string
 51		Cc           []string
 52		Summary      string
 53		Content      string
 54		InReplyTo    string
 55		Conversation string
 56		Published    time.Time
 57		Tag          []struct {
 58			Type string
 59			Name string
 60		}
 61		Attachment []struct {
 62			Type      string
 63			MediaType string
 64			Url       string
 65			Name      string
 66		}
 67	}
 68	var outbox struct {
 69		OrderedItems []struct {
 70			Object Toot
 71		}
 72	}
 73	fd, err := os.Open(source + "/outbox.json")
 74	if err != nil {
 75		log.Fatal(err)
 76	}
 77	dec := json.NewDecoder(fd)
 78	err = dec.Decode(&outbox)
 79	if err != nil {
 80		log.Fatalf("error parsing json: %s", err)
 81	}
 82	fd.Close()
 83
 84	havetoot := func(xid string) bool {
 85		var id int64
 86		row := stmtFindXonk.QueryRow(user.ID, xid)
 87		err := row.Scan(&id)
 88		if err == nil {
 89			return true
 90		}
 91		return false
 92	}
 93
 94	re_tootid := regexp.MustCompile("[^/]+$")
 95	for _, item := range outbox.OrderedItems {
 96		toot := item.Object
 97		tootid := re_tootid.FindString(toot.Id)
 98		xid := fmt.Sprintf("%s/%s/%s", user.URL, honkSep, tootid)
 99		if havetoot(xid) {
100			continue
101		}
102		honk := Honk{
103			UserID:   user.ID,
104			What:     "honk",
105			Honker:   user.URL,
106			XID:      xid,
107			RID:      toot.InReplyTo,
108			Date:     toot.Published,
109			URL:      xid,
110			Audience: append(toot.To, toot.Cc...),
111			Noise:    toot.Content,
112			Convoy:   toot.Conversation,
113			Whofore:  2,
114			Format:   "html",
115			Precis:   toot.Summary,
116		}
117		if honk.RID != "" {
118			honk.What = "tonk"
119		}
120		if !loudandproud(honk.Audience) {
121			honk.Whofore = 3
122		}
123		for _, att := range toot.Attachment {
124			switch att.Type {
125			case "Document":
126				fname := fmt.Sprintf("%s/%s", source, att.Url)
127				data, err := ioutil.ReadFile(fname)
128				if err != nil {
129					log.Printf("error reading media: %s", fname)
130					continue
131				}
132				u := xfiltrate()
133				name := att.Name
134				desc := name
135				newurl := fmt.Sprintf("https://%s/d/%s", serverName, u)
136				fileid, err := savefile(name, desc, newurl, att.MediaType, true, data)
137				if err != nil {
138					log.Printf("error saving media: %s", fname)
139					continue
140				}
141				donk := &Donk{
142					FileID: fileid,
143				}
144				honk.Donks = append(honk.Donks, donk)
145			}
146		}
147		for _, t := range toot.Tag {
148			switch t.Type {
149			case "Hashtag":
150				honk.Onts = append(honk.Onts, t.Name)
151			}
152		}
153		savehonk(&honk)
154	}
155}
156
157func importTwitter(username, source string) {
158	user, err := butwhatabout(username)
159	if err != nil {
160		log.Fatal(err)
161	}
162
163	type Tweet struct {
164		ID_str                  string
165		Created_at              string
166		Full_text               string
167		In_reply_to_screen_name string
168		In_reply_to_status_id   string
169		Entities                struct {
170			Hashtags []struct {
171				Text string
172			}
173			Media []struct {
174				Url       string
175				Media_url string
176			}
177			Urls []struct {
178				Url          string
179				Expanded_url string
180			}
181		}
182		date   time.Time
183		convoy string
184	}
185
186	var tweets []*Tweet
187	fd, err := os.Open(source + "/tweet.js")
188	if err != nil {
189		log.Fatal(err)
190	}
191	// skip past window.YTD.tweet.part0 =
192	fd.Seek(25, 0)
193	dec := json.NewDecoder(fd)
194	err = dec.Decode(&tweets)
195	if err != nil {
196		log.Fatalf("error parsing json: %s", err)
197	}
198	fd.Close()
199	tweetmap := make(map[string]*Tweet)
200	for _, t := range tweets {
201		t.date, _ = time.Parse("Mon Jan 02 15:04:05 -0700 2006", t.Created_at)
202		tweetmap[t.ID_str] = t
203	}
204	sort.Slice(tweets, func(i, j int) bool {
205		return tweets[i].date.Before(tweets[j].date)
206	})
207	havetwid := func(xid string) bool {
208		var id int64
209		row := stmtFindXonk.QueryRow(user.ID, xid)
210		err := row.Scan(&id)
211		if err == nil {
212			return true
213		}
214		return false
215	}
216
217	for _, t := range tweets {
218		xid := fmt.Sprintf("%s/%s/%s", user.URL, honkSep, t.ID_str)
219		if havetwid(xid) {
220			continue
221		}
222		what := "honk"
223		noise := ""
224		if parent := tweetmap[t.In_reply_to_status_id]; parent != nil {
225			t.convoy = parent.convoy
226			what = "tonk"
227		} else {
228			t.convoy = "data:,acoustichonkytonk-" + t.ID_str
229			if t.In_reply_to_screen_name != "" {
230				noise = fmt.Sprintf("re: https://twitter.com/%s/status/%s\n\n",
231					t.In_reply_to_screen_name, t.In_reply_to_status_id)
232				what = "tonk"
233			}
234		}
235		audience := []string{thewholeworld}
236		honk := Honk{
237			UserID:   user.ID,
238			Username: user.Name,
239			What:     what,
240			Honker:   user.URL,
241			XID:      xid,
242			Date:     t.date,
243			Format:   "markdown",
244			Audience: audience,
245			Convoy:   t.convoy,
246			Public:   true,
247			Whofore:  2,
248		}
249		noise += t.Full_text
250		// unbelievable
251		noise = html.UnescapeString(noise)
252		for _, r := range t.Entities.Urls {
253			noise = strings.Replace(noise, r.Url, r.Expanded_url, -1)
254		}
255		for _, m := range t.Entities.Media {
256			u := m.Media_url
257			idx := strings.LastIndexByte(u, '/')
258			u = u[idx+1:]
259			fname := fmt.Sprintf("%s/tweet_media/%s-%s", source, t.ID_str, u)
260			data, err := ioutil.ReadFile(fname)
261			if err != nil {
262				log.Printf("error reading media: %s", fname)
263				continue
264			}
265			newurl := fmt.Sprintf("https://%s/d/%s", serverName, u)
266
267			fileid, err := savefile(u, u, newurl, "image/jpg", true, data)
268			if err != nil {
269				log.Printf("error saving media: %s", fname)
270				continue
271			}
272			donk := &Donk{
273				FileID: fileid,
274			}
275			honk.Donks = append(honk.Donks, donk)
276			noise = strings.Replace(noise, m.Url, "", -1)
277		}
278		for _, ht := range t.Entities.Hashtags {
279			honk.Onts = append(honk.Onts, "#"+ht.Text)
280		}
281		honk.Noise = noise
282		savehonk(&honk)
283	}
284}