all repos — honk @ 4d706e02a810a41ecd16359374c65aef93c28e68

my fork of honk

activity.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	"bytes"
  20	"context"
  21	"crypto/tls"
  22	"database/sql"
  23	"fmt"
  24	"html"
  25	"io"
  26	notrand "math/rand"
  27	"net/http"
  28	"os"
  29	"regexp"
  30	"strings"
  31	"time"
  32
  33	"humungus.tedunangst.com/r/webs/cache"
  34	"humungus.tedunangst.com/r/webs/gate"
  35	"humungus.tedunangst.com/r/webs/httpsig"
  36	"humungus.tedunangst.com/r/webs/junk"
  37	"humungus.tedunangst.com/r/webs/templates"
  38)
  39
  40var theonetruename = `application/ld+json; profile="https://www.w3.org/ns/activitystreams"`
  41var thefakename = `application/activity+json`
  42var falsenames = []string{
  43	`application/ld+json`,
  44	`application/activity+json`,
  45}
  46var itiswhatitis = "https://www.w3.org/ns/activitystreams"
  47var thewholeworld = "https://www.w3.org/ns/activitystreams#Public"
  48
  49var fastTimeout time.Duration = 5
  50var slowTimeout time.Duration = 30
  51
  52func friendorfoe(ct string) bool {
  53	ct = strings.ToLower(ct)
  54	for _, at := range falsenames {
  55		if strings.HasPrefix(ct, at) {
  56			return true
  57		}
  58	}
  59	return false
  60}
  61
  62var honkClient = http.Client{}
  63
  64func gogglesDoNothing() {
  65	honkClient.Transport = &http.Transport{
  66		TLSClientConfig: &tls.Config{
  67			InsecureSkipVerify: true,
  68		},
  69	}
  70}
  71
  72func PostJunk(keyname string, key httpsig.PrivateKey, url string, j junk.Junk) error {
  73	return PostMsg(keyname, key, url, j.ToBytes())
  74}
  75
  76func PostMsg(keyname string, key httpsig.PrivateKey, url string, msg []byte) error {
  77	req, err := http.NewRequest("POST", url, bytes.NewReader(msg))
  78	if err != nil {
  79		return err
  80	}
  81	req.Header.Set("User-Agent", "honksnonk/5.0; "+serverName)
  82	req.Header.Set("Content-Type", theonetruename)
  83	httpsig.SignRequest(keyname, key, req, msg)
  84	ctx, cancel := context.WithTimeout(context.Background(), 2*slowTimeout*time.Second)
  85	defer cancel()
  86	req = req.WithContext(ctx)
  87	resp, err := honkClient.Do(req)
  88	if err != nil {
  89		return err
  90	}
  91	resp.Body.Close()
  92	switch resp.StatusCode {
  93	case 200:
  94	case 201:
  95	case 202:
  96	default:
  97		return fmt.Errorf("http post status: %d", resp.StatusCode)
  98	}
  99	ilog.Printf("successful post: %s %d", url, resp.StatusCode)
 100	return nil
 101}
 102
 103func GetJunk(userid int64, url string) (junk.Junk, error) {
 104	return GetJunkTimeout(userid, url, slowTimeout*time.Second)
 105}
 106
 107func GetJunkFast(userid int64, url string) (junk.Junk, error) {
 108	return GetJunkTimeout(userid, url, fastTimeout*time.Second)
 109}
 110
 111func GetJunkHardMode(userid int64, url string) (junk.Junk, error) {
 112	j, err := GetJunk(userid, url)
 113	if err != nil {
 114		emsg := err.Error()
 115		if emsg == "http get status: 502" || strings.Contains(emsg, "timeout") {
 116			ilog.Printf("trying again after error: %s", emsg)
 117			time.Sleep(time.Duration(60+notrand.Int63n(60)) * time.Second)
 118			j, err = GetJunk(userid, url)
 119			if err != nil {
 120				ilog.Printf("still couldn't get it")
 121			} else {
 122				ilog.Printf("retry success!")
 123			}
 124		}
 125	}
 126	return j, err
 127}
 128
 129var flightdeck = gate.NewSerializer()
 130
 131func GetJunkTimeout(userid int64, url string, timeout time.Duration) (junk.Junk, error) {
 132	if rejectorigin(userid, url, false) {
 133		return nil, fmt.Errorf("rejected origin: %s", url)
 134	}
 135	sign := func(req *http.Request) error {
 136		var ki *KeyInfo
 137		ok := ziggies.Get(userid, &ki)
 138		if ok {
 139			httpsig.SignRequest(ki.keyname, ki.seckey, req, nil)
 140		}
 141		return nil
 142	}
 143	if develMode {
 144		sign = nil
 145	}
 146	client := honkClient
 147	client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
 148		if len(via) >= 5 {
 149			return fmt.Errorf("stopped after 5 redirects")
 150		}
 151		if sign != nil {
 152			sign(req)
 153		}
 154		return nil
 155	}
 156	fn := func() (interface{}, error) {
 157		at := theonetruename
 158		if strings.Contains(url, ".well-known/webfinger?resource") {
 159			at = "application/jrd+json"
 160		}
 161		j, err := junk.Get(url, junk.GetArgs{
 162			Accept:  at,
 163			Agent:   "honksnonk/5.0; " + serverName,
 164			Timeout: timeout,
 165			Client:  &client,
 166			Fixup:   sign,
 167		})
 168		return j, err
 169	}
 170
 171	ji, err := flightdeck.Call(url, fn)
 172	if err != nil {
 173		return nil, err
 174	}
 175	j := ji.(junk.Junk)
 176	return j, nil
 177}
 178
 179func fetchsome(url string) ([]byte, error) {
 180	req, err := http.NewRequest("GET", url, nil)
 181	if err != nil {
 182		ilog.Printf("error fetching %s: %s", url, err)
 183		return nil, err
 184	}
 185	req.Header.Set("User-Agent", "honksnonk/5.0; "+serverName)
 186	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
 187	defer cancel()
 188	req = req.WithContext(ctx)
 189	resp, err := honkClient.Do(req)
 190	if err != nil {
 191		ilog.Printf("error fetching %s: %s", url, err)
 192		return nil, err
 193	}
 194	defer resp.Body.Close()
 195	switch resp.StatusCode {
 196	case 200:
 197	case 201:
 198	case 202:
 199	default:
 200		return nil, fmt.Errorf("http get not 200: %d %s", resp.StatusCode, url)
 201	}
 202	var buf bytes.Buffer
 203	limiter := io.LimitReader(resp.Body, 10*1024*1024)
 204	io.Copy(&buf, limiter)
 205	return buf.Bytes(), nil
 206}
 207
 208func savedonk(url string, name, desc, media string, localize bool) *Donk {
 209	if url == "" {
 210		return nil
 211	}
 212	if donk := finddonk(url); donk != nil {
 213		return donk
 214	}
 215	ilog.Printf("saving donk: %s", url)
 216	data := []byte{}
 217	if localize {
 218		fn := func() (interface{}, error) {
 219			return fetchsome(url)
 220		}
 221		ii, err := flightdeck.Call(url, fn)
 222		if err != nil {
 223			ilog.Printf("error fetching donk: %s", err)
 224			localize = false
 225			goto saveit
 226		}
 227		data = ii.([]byte)
 228
 229		if len(data) == 10*1024*1024 {
 230			ilog.Printf("truncation likely")
 231		}
 232		if strings.HasPrefix(media, "image") {
 233			img, err := shrinkit(data)
 234			if err != nil {
 235				ilog.Printf("unable to decode image: %s", err)
 236				localize = false
 237				data = []byte{}
 238				goto saveit
 239			}
 240			data = img.Data
 241			media = "image/" + img.Format
 242		} else if media == "application/pdf" {
 243			if len(data) > 1000000 {
 244				ilog.Printf("not saving large pdf")
 245				localize = false
 246				data = []byte{}
 247			}
 248		} else if len(data) > 100000 {
 249			ilog.Printf("not saving large attachment")
 250			localize = false
 251			data = []byte{}
 252		}
 253	}
 254saveit:
 255	fileid, err := savefile(name, desc, url, media, localize, data)
 256	if err != nil {
 257		elog.Printf("error saving file %s: %s", url, err)
 258		return nil
 259	}
 260	donk := new(Donk)
 261	donk.FileID = fileid
 262	return donk
 263}
 264
 265func iszonked(userid int64, xid string) bool {
 266	var id int64
 267	row := stmtFindZonk.QueryRow(userid, xid)
 268	err := row.Scan(&id)
 269	if err == nil {
 270		return true
 271	}
 272	if err != sql.ErrNoRows {
 273		ilog.Printf("error querying zonk: %s", err)
 274	}
 275	return false
 276}
 277
 278func needxonk(user *WhatAbout, x *Honk) bool {
 279	if rejectxonk(x) {
 280		return false
 281	}
 282	return needxonkid(user, x.XID)
 283}
 284func needbonkid(user *WhatAbout, xid string) bool {
 285	return needxonkidX(user, xid, true)
 286}
 287func needxonkid(user *WhatAbout, xid string) bool {
 288	return needxonkidX(user, xid, false)
 289}
 290func needxonkidX(user *WhatAbout, xid string, isannounce bool) bool {
 291	if !strings.HasPrefix(xid, "https://") {
 292		return false
 293	}
 294	if strings.HasPrefix(xid, user.URL+"/") {
 295		return false
 296	}
 297	if rejectorigin(user.ID, xid, isannounce) {
 298		ilog.Printf("rejecting origin: %s", xid)
 299		return false
 300	}
 301	if iszonked(user.ID, xid) {
 302		ilog.Printf("already zonked: %s", xid)
 303		return false
 304	}
 305	var id int64
 306	row := stmtFindXonk.QueryRow(user.ID, xid)
 307	err := row.Scan(&id)
 308	if err == nil {
 309		return false
 310	}
 311	if err != sql.ErrNoRows {
 312		ilog.Printf("error querying xonk: %s", err)
 313	}
 314	return true
 315}
 316
 317func eradicatexonk(userid int64, xid string) {
 318	xonk := getxonk(userid, xid)
 319	if xonk != nil {
 320		deletehonk(xonk.ID)
 321		_, err := stmtSaveZonker.Exec(userid, xid, "zonk")
 322		if err != nil {
 323			elog.Printf("error eradicating: %s", err)
 324		}
 325	}
 326}
 327
 328func savexonk(x *Honk) {
 329	ilog.Printf("saving xonk: %s", x.XID)
 330	go handles(x.Honker)
 331	go handles(x.Oonker)
 332	savehonk(x)
 333}
 334
 335type Box struct {
 336	In     string
 337	Out    string
 338	Shared string
 339}
 340
 341var boxofboxes = cache.New(cache.Options{Filler: func(ident string) (*Box, bool) {
 342	var info string
 343	row := stmtGetXonker.QueryRow(ident, "boxes")
 344	err := row.Scan(&info)
 345	if err != nil {
 346		dlog.Printf("need to get boxes for %s", ident)
 347		var j junk.Junk
 348		j, err = GetJunk(readyLuserOne, ident)
 349		if err != nil {
 350			dlog.Printf("error getting boxes: %s", err)
 351			return nil, false
 352		}
 353		allinjest(originate(ident), j)
 354		row = stmtGetXonker.QueryRow(ident, "boxes")
 355		err = row.Scan(&info)
 356	}
 357	if err == nil {
 358		m := strings.Split(info, " ")
 359		b := &Box{In: m[0], Out: m[1], Shared: m[2]}
 360		return b, true
 361	}
 362	return nil, false
 363}})
 364
 365func gimmexonks(user *WhatAbout, outbox string) {
 366	dlog.Printf("getting outbox: %s", outbox)
 367	j, err := GetJunk(user.ID, outbox)
 368	if err != nil {
 369		ilog.Printf("error getting outbox: %s", err)
 370		return
 371	}
 372	t, _ := j.GetString("type")
 373	origin := originate(outbox)
 374	if t == "OrderedCollection" {
 375		items, _ := j.GetArray("orderedItems")
 376		if items == nil {
 377			items, _ = j.GetArray("items")
 378		}
 379		if items == nil {
 380			obj, ok := j.GetMap("first")
 381			if ok {
 382				items, _ = obj.GetArray("orderedItems")
 383			} else {
 384				page1, ok := j.GetString("first")
 385				if ok {
 386					j, err = GetJunk(user.ID, page1)
 387					if err != nil {
 388						ilog.Printf("error getting page1: %s", err)
 389						return
 390					}
 391					items, _ = j.GetArray("orderedItems")
 392				}
 393			}
 394		}
 395		if len(items) > 20 {
 396			items = items[0:20]
 397		}
 398		for i, j := 0, len(items)-1; i < j; i, j = i+1, j-1 {
 399			items[i], items[j] = items[j], items[i]
 400		}
 401		for _, item := range items {
 402			obj, ok := item.(junk.Junk)
 403			if ok {
 404				xonksaver(user, obj, origin)
 405				continue
 406			}
 407			xid, ok := item.(string)
 408			if ok {
 409				if !needxonkid(user, xid) {
 410					continue
 411				}
 412				obj, err = GetJunk(user.ID, xid)
 413				if err != nil {
 414					ilog.Printf("error getting item: %s", err)
 415					continue
 416				}
 417				xonksaver(user, obj, originate(xid))
 418			}
 419		}
 420	}
 421}
 422
 423func newphone(a []string, obj junk.Junk) []string {
 424	for _, addr := range []string{"to", "cc", "attributedTo"} {
 425		who, _ := obj.GetString(addr)
 426		if who != "" {
 427			a = append(a, who)
 428		}
 429		whos, _ := obj.GetArray(addr)
 430		for _, w := range whos {
 431			who, _ := w.(string)
 432			if who != "" {
 433				a = append(a, who)
 434			}
 435		}
 436	}
 437	return a
 438}
 439
 440func extractattrto(obj junk.Junk) string {
 441	arr := oneforall(obj, "attributedTo")
 442	for _, a := range arr {
 443		s, ok := a.(string)
 444		if ok {
 445			return s
 446		}
 447		o, ok := a.(junk.Junk)
 448		if ok {
 449			t, _ := o.GetString("type")
 450			id, _ := o.GetString("id")
 451			if t == "Person" || t == "" {
 452				return id
 453			}
 454		}
 455	}
 456	return ""
 457}
 458
 459func oneforall(obj junk.Junk, key string) []interface{} {
 460	if val, ok := obj.GetMap(key); ok {
 461		return []interface{}{val}
 462	}
 463	if str, ok := obj.GetString(key); ok {
 464		return []interface{}{str}
 465	}
 466	arr, _ := obj.GetArray(key)
 467	return arr
 468}
 469
 470func firstofmany(obj junk.Junk, key string) string {
 471	if val, _ := obj.GetString(key); val != "" {
 472		return val
 473	}
 474	if arr, _ := obj.GetArray(key); len(arr) > 0 {
 475		val, ok := arr[0].(string)
 476		if ok {
 477			return val
 478		}
 479	}
 480	return ""
 481}
 482
 483var re_mast0link = regexp.MustCompile(`https://[[:alnum:].]+/users/[[:alnum:]]+/statuses/[[:digit:]]+`)
 484var re_masto1ink = regexp.MustCompile(`https://([[:alnum:].]+)/@([[:alnum:]]+)/([[:digit:]]+)`)
 485var re_misslink = regexp.MustCompile(`https://[[:alnum:].]+/notes/[[:alnum:]]+`)
 486var re_honklink = regexp.MustCompile(`https://[[:alnum:].]+/u/[[:alnum:]]+/h/[[:alnum:]]+`)
 487var re_r0malink = regexp.MustCompile(`https://[[:alnum:].]+/objects/[[:alnum:]-]+`)
 488var re_roma1ink = regexp.MustCompile(`https://[[:alnum:].]+/notice/[[:alnum:]]+`)
 489var re_qtlinks = regexp.MustCompile(`>https://[^\s<]+<`)
 490
 491func xonksaver(user *WhatAbout, item junk.Junk, origin string) *Honk {
 492	depth := 0
 493	maxdepth := 10
 494	currenttid := ""
 495	goingup := 0
 496	var xonkxonkfn func(junk.Junk, string, bool, string) *Honk
 497
 498	qutify := func(user *WhatAbout, qurl, content string) string {
 499		if depth >= maxdepth {
 500			ilog.Printf("in too deep")
 501			return content
 502		}
 503		// well this is gross
 504		malcontent := strings.ReplaceAll(content, `</span><span class="ellipsis">`, "")
 505		malcontent = strings.ReplaceAll(malcontent, `</span><span class="invisible">`, "")
 506		mlinks := re_qtlinks.FindAllString(malcontent, -1)
 507		if qurl != "" {
 508			mlinks = append(mlinks, ">"+qurl+"<")
 509		}
 510		for _, m := range mlinks {
 511			tryit := false
 512			m = m[1 : len(m)-1]
 513			if re_mast0link.MatchString(m) || re_misslink.MatchString(m) ||
 514				re_honklink.MatchString(m) || re_r0malink.MatchString(m) ||
 515				re_roma1ink.MatchString(m) {
 516				tryit = true
 517			} else if re_masto1ink.MatchString(m) {
 518				m = re_masto1ink.ReplaceAllString(m, "https://$1/users/$2/statuses/$3")
 519				tryit = true
 520			}
 521			if tryit {
 522				var prefix string
 523				if m == qurl {
 524					prefix += fmt.Sprintf("<p><a href=\"%s\">%s</a>", m, m)
 525				}
 526				if x := getxonk(user.ID, m); x != nil {
 527					content = fmt.Sprintf("%s%s<blockquote>%s</blockquote>", content, prefix, x.Noise)
 528				} else if j, err := GetJunk(user.ID, m); err == nil {
 529					q, ok := j.GetString("content")
 530					if ok {
 531						content = fmt.Sprintf("%s%s<blockquote>%s</blockquote>", content, prefix, q)
 532					}
 533					prevdepth := depth
 534					depth = maxdepth
 535					xonkxonkfn(j, originate(m), false, "")
 536					depth = prevdepth
 537				}
 538			}
 539		}
 540		return content
 541	}
 542
 543	saveonemore := func(xid string) {
 544		dlog.Printf("getting onemore: %s", xid)
 545		if depth >= maxdepth {
 546			ilog.Printf("in too deep")
 547			return
 548		}
 549		obj, err := GetJunkHardMode(user.ID, xid)
 550		if err != nil {
 551			ilog.Printf("error getting onemore: %s: %s", xid, err)
 552			return
 553		}
 554		xonkxonkfn(obj, originate(xid), false, "")
 555	}
 556
 557	xonkxonkfn = func(item junk.Junk, origin string, isUpdate bool, bonker string) *Honk {
 558		id, _ := item.GetString("id")
 559		what := firstofmany(item, "type")
 560		dt, ok := item.GetString("published")
 561		if !ok {
 562			dt = time.Now().Format(time.RFC3339)
 563		}
 564		if depth >= maxdepth+5 {
 565			ilog.Printf("went too deep in xonkxonk")
 566			return nil
 567		}
 568		depth++
 569		defer func() { depth-- }()
 570
 571		var err error
 572		var xid, rid, url, convoy string
 573		var replies []string
 574		var obj junk.Junk
 575		waspage := false
 576		preferorig := false
 577		switch what {
 578		case "Delete":
 579			obj, ok = item.GetMap("object")
 580			if ok {
 581				xid, _ = obj.GetString("id")
 582			} else {
 583				xid, _ = item.GetString("object")
 584			}
 585			if xid == "" {
 586				return nil
 587			}
 588			if originate(xid) != origin {
 589				ilog.Printf("forged delete: %s", xid)
 590				return nil
 591			}
 592			ilog.Printf("eradicating %s", xid)
 593			eradicatexonk(user.ID, xid)
 594			return nil
 595		case "Remove":
 596			xid, _ = item.GetString("object")
 597			targ, _ := obj.GetString("target")
 598			ilog.Printf("remove %s from %s", obj, targ)
 599			return nil
 600		case "Tombstone":
 601			xid, _ = item.GetString("id")
 602			if xid == "" {
 603				return nil
 604			}
 605			if originate(xid) != origin {
 606				ilog.Printf("forged delete: %s", xid)
 607				return nil
 608			}
 609			ilog.Printf("eradicating %s", xid)
 610			eradicatexonk(user.ID, xid)
 611			return nil
 612		case "Announce":
 613			obj, ok = item.GetMap("object")
 614			if ok {
 615				// peek ahead some
 616				what := firstofmany(obj, "type")
 617				if what == "Create" || what == "Update" {
 618					if what == "Update" {
 619						isUpdate = true
 620					}
 621					inner, ok := obj.GetMap("object")
 622					if ok {
 623						obj = inner
 624					} else {
 625						xid, _ = obj.GetString("object")
 626					}
 627				}
 628				if xid == "" {
 629					xid, _ = obj.GetString("id")
 630				}
 631			} else {
 632				xid, _ = item.GetString("object")
 633			}
 634			if !isUpdate && !needbonkid(user, xid) {
 635				return nil
 636			}
 637			bonker, _ = item.GetString("actor")
 638			origin = originate(xid)
 639			if ok && originate(id) == origin {
 640				dlog.Printf("using object in announce for %s", xid)
 641			} else {
 642				dlog.Printf("getting bonk: %s", xid)
 643				obj, err = GetJunkHardMode(user.ID, xid)
 644				if err != nil {
 645					ilog.Printf("error getting bonk: %s: %s", xid, err)
 646					return nil
 647				}
 648			}
 649			return xonkxonkfn(obj, origin, isUpdate, bonker)
 650		case "Update":
 651			isUpdate = true
 652			fallthrough
 653		case "Create":
 654			obj, ok = item.GetMap("object")
 655			if !ok {
 656				xid, _ = item.GetString("object")
 657				dlog.Printf("getting created honk: %s", xid)
 658				if originate(xid) != origin {
 659					ilog.Printf("out of bounds %s not from %s", xid, origin)
 660					return nil
 661				}
 662				obj, err = GetJunkHardMode(user.ID, xid)
 663				if err != nil {
 664					ilog.Printf("error getting creation: %s", err)
 665				}
 666			}
 667			if obj == nil {
 668				ilog.Printf("no object for creation %s", id)
 669				return nil
 670			}
 671			return xonkxonkfn(obj, origin, isUpdate, bonker)
 672		case "Read":
 673			xid, ok = item.GetString("object")
 674			if ok {
 675				if !needxonkid(user, xid) {
 676					dlog.Printf("don't need read obj: %s", xid)
 677					return nil
 678				}
 679				obj, err = GetJunkHardMode(user.ID, xid)
 680				if err != nil {
 681					ilog.Printf("error getting read: %s", err)
 682					return nil
 683				}
 684				return xonkxonkfn(obj, originate(xid), false, "")
 685			}
 686			return nil
 687		case "Add":
 688			xid, ok = item.GetString("object")
 689			if ok {
 690				// check target...
 691				if !needxonkid(user, xid) {
 692					dlog.Printf("don't need added obj: %s", xid)
 693					return nil
 694				}
 695				obj, err = GetJunkHardMode(user.ID, xid)
 696				if err != nil {
 697					ilog.Printf("error getting add: %s", err)
 698					return nil
 699				}
 700				return xonkxonkfn(obj, originate(xid), false, "")
 701			}
 702			return nil
 703		case "Move":
 704			obj = item
 705			what = "move"
 706		case "Page":
 707			waspage = true
 708			fallthrough
 709		case "Audio":
 710			fallthrough
 711		case "Image":
 712			if what == "Image" {
 713				preferorig = true
 714			}
 715			fallthrough
 716		case "Video":
 717			fallthrough
 718		case "Question":
 719			fallthrough
 720		case "Note":
 721			fallthrough
 722		case "Article":
 723			obj = item
 724			what = "honk"
 725		case "Event":
 726			obj = item
 727			what = "event"
 728		case "ChatMessage":
 729			bonker = ""
 730			obj = item
 731			what = "chonk"
 732		default:
 733			ilog.Printf("unknown activity: %s", what)
 734			dumpactivity(item)
 735			return nil
 736		}
 737		if bonker != "" {
 738			what = "bonk"
 739		}
 740
 741		if obj != nil {
 742			xid, _ = obj.GetString("id")
 743		}
 744
 745		if xid == "" {
 746			ilog.Printf("don't know what xid is")
 747			item.Write(ilog.Writer())
 748			return nil
 749		}
 750		if originate(xid) != origin {
 751			if !develMode && origin != "" {
 752				ilog.Printf("original sin: %s not from %s", xid, origin)
 753				item.Write(ilog.Writer())
 754				return nil
 755			}
 756		}
 757
 758		var xonk Honk
 759		// early init
 760		xonk.XID = xid
 761		xonk.UserID = user.ID
 762		xonk.Honker, _ = item.GetString("actor")
 763		if xonk.Honker == "" {
 764			xonk.Honker, _ = item.GetString("attributedTo")
 765		}
 766		if obj != nil {
 767			if xonk.Honker == "" {
 768				xonk.Honker = extractattrto(obj)
 769			}
 770			if bonker != "" {
 771				xonk.Honker, xonk.Oonker = bonker, xonk.Honker
 772			}
 773			if xonk.Oonker == xonk.Honker {
 774				xonk.Oonker = ""
 775			}
 776			xonk.Audience = newphone(nil, obj)
 777		}
 778		xonk.Audience = append(xonk.Audience, xonk.Honker)
 779		xonk.Audience = oneofakind(xonk.Audience)
 780		xonk.Public = loudandproud(xonk.Audience)
 781
 782		var mentions []Mention
 783		if obj != nil {
 784			ot := firstofmany(obj, "type")
 785			url, _ = obj.GetString("url")
 786			if dt2, ok := obj.GetString("published"); ok {
 787				dt = dt2
 788			}
 789			content, _ := obj.GetString("content")
 790			if !strings.HasPrefix(content, "<p>") {
 791				content = "<p>" + content
 792			}
 793			precis, _ := obj.GetString("summary")
 794			if name, ok := obj.GetString("name"); ok {
 795				if precis != "" {
 796					content = precis + "<p>" + content
 797				}
 798				precis = html.EscapeString(name)
 799			}
 800			if sens, _ := obj["sensitive"].(bool); sens && precis == "" {
 801				precis = "unspecified horror"
 802			}
 803			if waspage {
 804				content += fmt.Sprintf(`<p><a href="%s">%s</a>`, url, url)
 805				url = xid
 806			}
 807			if user.Options.InlineQuotes {
 808				qurl, _ := obj.GetString("quoteUrl")
 809				content = qutify(user, qurl, content)
 810			}
 811			rid, ok = obj.GetString("inReplyTo")
 812			if !ok {
 813				if robj, ok := obj.GetMap("inReplyTo"); ok {
 814					rid, _ = robj.GetString("id")
 815				}
 816			}
 817			convoy, _ = obj.GetString("context")
 818			if convoy == "" {
 819				convoy, _ = obj.GetString("conversation")
 820			}
 821			if ot == "Question" {
 822				if what == "honk" {
 823					what = "qonk"
 824				}
 825				content += "<ul>"
 826				ans, _ := obj.GetArray("oneOf")
 827				for _, ai := range ans {
 828					a, ok := ai.(junk.Junk)
 829					if !ok {
 830						continue
 831					}
 832					as, _ := a.GetString("name")
 833					content += "<li>" + as
 834				}
 835				ans, _ = obj.GetArray("anyOf")
 836				for _, ai := range ans {
 837					a, ok := ai.(junk.Junk)
 838					if !ok {
 839						continue
 840					}
 841					as, _ := a.GetString("name")
 842					content += "<li>" + as
 843				}
 844				content += "</ul>"
 845			}
 846			if ot == "Move" {
 847				targ, _ := obj.GetString("target")
 848				content += string(templates.Sprintf(`<p>Moved to <a href="%s">%s</a>`, targ, targ))
 849			}
 850			if len(content) > 90001 {
 851				ilog.Printf("content too long. truncating")
 852				content = content[:90001]
 853			}
 854
 855			xonk.Noise = content
 856			xonk.Precis = precis
 857			if rejectxonk(&xonk) {
 858				dlog.Printf("fast reject: %s", xid)
 859				return nil
 860			}
 861
 862			numatts := 0
 863			procatt := func(att junk.Junk) {
 864				at, _ := att.GetString("type")
 865				mt, _ := att.GetString("mediaType")
 866				if mt == "" {
 867					mt = "image"
 868				}
 869				u, ok := att.GetString("url")
 870				if !ok {
 871					u, ok = att.GetString("href")
 872				}
 873				if !ok {
 874					if ua, ok := att.GetArray("url"); ok && len(ua) > 0 {
 875						u, ok = ua[0].(string)
 876						if !ok {
 877							if uu, ok := ua[0].(junk.Junk); ok {
 878								u, _ = uu.GetString("href")
 879								if mt == "" {
 880									mt, _ = uu.GetString("mediaType")
 881								}
 882							}
 883						}
 884					} else if uu, ok := att.GetMap("url"); ok {
 885						u, _ = uu.GetString("href")
 886						if mt == "" {
 887							mt, _ = uu.GetString("mediaType")
 888						}
 889					}
 890				}
 891				name, _ := att.GetString("name")
 892				desc, _ := att.GetString("summary")
 893				desc = html.UnescapeString(desc)
 894				if desc == "" {
 895					desc = name
 896				}
 897				localize := false
 898				if at == "Document" || at == "Image" {
 899					mt = strings.ToLower(mt)
 900					dlog.Printf("attachment: %s %s", mt, u)
 901					if mt == "text/plain" || mt == "application/pdf" ||
 902						strings.HasPrefix(mt, "image") {
 903						if numatts > 4 {
 904							ilog.Printf("excessive attachment: %s", at)
 905						} else {
 906							localize = true
 907						}
 908					}
 909				} else if at == "Link" {
 910					if waspage {
 911						xonk.Noise += fmt.Sprintf(`<p><a href="%s">%s</a>`, u, u)
 912						return
 913					}
 914					if name == "" {
 915						name = u
 916					}
 917				} else {
 918					ilog.Printf("unknown attachment: %s", at)
 919				}
 920				if skipMedia(&xonk) {
 921					localize = false
 922				}
 923				if preferorig && !localize {
 924					return
 925				}
 926				donk := savedonk(u, name, desc, mt, localize)
 927				if donk != nil {
 928					xonk.Donks = append(xonk.Donks, donk)
 929				}
 930				numatts++
 931			}
 932			if img, ok := obj.GetMap("image"); ok {
 933				procatt(img)
 934			}
 935			if preferorig {
 936				atts, _ := obj.GetArray("url")
 937				for _, atti := range atts {
 938					att, ok := atti.(junk.Junk)
 939					if !ok {
 940						ilog.Printf("attachment that wasn't map?")
 941						continue
 942					}
 943					procatt(att)
 944				}
 945				if numatts == 0 {
 946					preferorig = false
 947				}
 948			}
 949			if !preferorig {
 950				atts := oneforall(obj, "attachment")
 951				for _, atti := range atts {
 952					att, ok := atti.(junk.Junk)
 953					if !ok {
 954						ilog.Printf("attachment that wasn't map?")
 955						continue
 956					}
 957					procatt(att)
 958				}
 959			}
 960			proctag := func(tag junk.Junk) {
 961				tt, _ := tag.GetString("type")
 962				name, _ := tag.GetString("name")
 963				desc, _ := tag.GetString("summary")
 964				desc = html.UnescapeString(desc)
 965				if desc == "" {
 966					desc = name
 967				}
 968				if tt == "Emoji" {
 969					icon, _ := tag.GetMap("icon")
 970					mt, _ := icon.GetString("mediaType")
 971					if mt == "" {
 972						mt = "image/png"
 973					}
 974					u, _ := icon.GetString("url")
 975					donk := savedonk(u, name, desc, mt, true)
 976					if donk != nil {
 977						xonk.Donks = append(xonk.Donks, donk)
 978					}
 979				}
 980				if tt == "Hashtag" {
 981					if name == "" || name == "#" {
 982						// skip it
 983					} else {
 984						if name[0] != '#' {
 985							name = "#" + name
 986						}
 987						xonk.Onts = append(xonk.Onts, name)
 988					}
 989				}
 990				if tt == "Place" {
 991					p := new(Place)
 992					p.Name = name
 993					p.Latitude, _ = tag.GetNumber("latitude")
 994					p.Longitude, _ = tag.GetNumber("longitude")
 995					p.Url, _ = tag.GetString("url")
 996					xonk.Place = p
 997				}
 998				if tt == "Mention" {
 999					var m Mention
1000					m.Who, _ = tag.GetString("name")
1001					m.Where, _ = tag.GetString("href")
1002					mentions = append(mentions, m)
1003				}
1004			}
1005			tags := oneforall(obj, "tag")
1006			for _, tagi := range tags {
1007				tag, ok := tagi.(junk.Junk)
1008				if !ok {
1009					continue
1010				}
1011				proctag(tag)
1012			}
1013			if starttime, ok := obj.GetString("startTime"); ok {
1014				if start, err := time.Parse(time.RFC3339, starttime); err == nil {
1015					t := new(Time)
1016					t.StartTime = start
1017					endtime, _ := obj.GetString("endTime")
1018					t.EndTime, _ = time.Parse(time.RFC3339, endtime)
1019					dura, _ := obj.GetString("duration")
1020					if strings.HasPrefix(dura, "PT") {
1021						dura = strings.ToLower(dura[2:])
1022						d, _ := time.ParseDuration(dura)
1023						t.Duration = Duration(d)
1024					}
1025					xonk.Time = t
1026				}
1027			}
1028			if loca, ok := obj.GetMap("location"); ok {
1029				if tt, _ := loca.GetString("type"); tt == "Place" {
1030					p := new(Place)
1031					p.Name, _ = loca.GetString("name")
1032					p.Latitude, _ = loca.GetNumber("latitude")
1033					p.Longitude, _ = loca.GetNumber("longitude")
1034					p.Url, _ = loca.GetString("url")
1035					xonk.Place = p
1036				}
1037			}
1038
1039			xonk.Onts = oneofakind(xonk.Onts)
1040			replyobj, ok := obj.GetMap("replies")
1041			if ok {
1042				items, ok := replyobj.GetArray("items")
1043				if !ok {
1044					first, ok := replyobj.GetMap("first")
1045					if ok {
1046						items, _ = first.GetArray("items")
1047					}
1048				}
1049				for _, repl := range items {
1050					s, ok := repl.(string)
1051					if ok {
1052						replies = append(replies, s)
1053					}
1054				}
1055			}
1056
1057		}
1058
1059		if currenttid == "" {
1060			currenttid = convoy
1061		}
1062
1063		// init xonk
1064		xonk.What = what
1065		xonk.RID = rid
1066		xonk.Date, _ = time.Parse(time.RFC3339, dt)
1067		xonk.URL = url
1068		xonk.Format = "html"
1069		xonk.Convoy = convoy
1070		xonk.Mentions = mentions
1071		for _, m := range mentions {
1072			if m.Where == user.URL {
1073				xonk.Whofore = 1
1074			}
1075		}
1076		imaginate(&xonk)
1077
1078		if what == "chonk" {
1079			target, _ := obj.GetString("to")
1080			if target == user.URL {
1081				target = xonk.Honker
1082			}
1083			ch := Chonk{
1084				UserID: xonk.UserID,
1085				XID:    xid,
1086				Who:    xonk.Honker,
1087				Target: target,
1088				Date:   xonk.Date,
1089				Noise:  xonk.Noise,
1090				Format: xonk.Format,
1091				Donks:  xonk.Donks,
1092			}
1093			savechonk(&ch)
1094			return nil
1095		}
1096
1097		if isUpdate {
1098			dlog.Printf("something has changed! %s", xonk.XID)
1099			prev := getxonk(user.ID, xonk.XID)
1100			if prev == nil {
1101				ilog.Printf("didn't find old version for update: %s", xonk.XID)
1102				isUpdate = false
1103			} else {
1104				xonk.ID = prev.ID
1105				updatehonk(&xonk)
1106			}
1107		}
1108		if !isUpdate && needxonk(user, &xonk) {
1109			if rid != "" && xonk.Public {
1110				if needxonkid(user, rid) {
1111					goingup++
1112					saveonemore(rid)
1113					goingup--
1114				}
1115				if convoy == "" {
1116					xx := getxonk(user.ID, rid)
1117					if xx != nil {
1118						convoy = xx.Convoy
1119					}
1120				}
1121			}
1122			if convoy == "" {
1123				convoy = currenttid
1124			}
1125			if convoy == "" {
1126				convoy = xonk.XID
1127				currenttid = convoy
1128			}
1129			xonk.Convoy = convoy
1130			savexonk(&xonk)
1131		}
1132		if goingup == 0 {
1133			for _, replid := range replies {
1134				if needxonkid(user, replid) {
1135					dlog.Printf("missing a reply: %s", replid)
1136					saveonemore(replid)
1137				}
1138			}
1139		}
1140		return &xonk
1141	}
1142
1143	return xonkxonkfn(item, origin, false, "")
1144}
1145
1146func dumpactivity(item junk.Junk) {
1147	fd, err := os.OpenFile("savedinbox.json", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
1148	if err != nil {
1149		elog.Printf("error opening inbox! %s", err)
1150		return
1151	}
1152	defer fd.Close()
1153	item.Write(fd)
1154	io.WriteString(fd, "\n")
1155}
1156
1157func rubadubdub(user *WhatAbout, req junk.Junk) {
1158	actor, _ := req.GetString("actor")
1159	j := junk.New()
1160	j["@context"] = itiswhatitis
1161	j["id"] = user.URL + "/dub/" + xfiltrate()
1162	j["type"] = "Accept"
1163	j["actor"] = user.URL
1164	j["to"] = actor
1165	j["published"] = time.Now().UTC().Format(time.RFC3339)
1166	j["object"] = req
1167
1168	deliverate(user.ID, actor, j.ToBytes())
1169}
1170
1171func itakeitallback(user *WhatAbout, xid string, owner string, folxid string) {
1172	j := junk.New()
1173	j["@context"] = itiswhatitis
1174	j["id"] = user.URL + "/unsub/" + folxid
1175	j["type"] = "Undo"
1176	j["actor"] = user.URL
1177	j["to"] = owner
1178	f := junk.New()
1179	f["id"] = user.URL + "/sub/" + folxid
1180	f["type"] = "Follow"
1181	f["actor"] = user.URL
1182	f["to"] = owner
1183	f["object"] = xid
1184	j["object"] = f
1185	j["published"] = time.Now().UTC().Format(time.RFC3339)
1186
1187	deliverate(user.ID, owner, j.ToBytes())
1188}
1189
1190func subsub(user *WhatAbout, xid string, owner string, folxid string) {
1191	if xid == "" {
1192		ilog.Printf("can't subscribe to empty")
1193		return
1194	}
1195	j := junk.New()
1196	j["@context"] = itiswhatitis
1197	j["id"] = user.URL + "/sub/" + folxid
1198	j["type"] = "Follow"
1199	j["actor"] = user.URL
1200	j["to"] = owner
1201	j["object"] = xid
1202	j["published"] = time.Now().UTC().Format(time.RFC3339)
1203
1204	deliverate(user.ID, owner, j.ToBytes())
1205}
1206
1207func activatedonks(donks []*Donk) []junk.Junk {
1208	var atts []junk.Junk
1209	for _, d := range donks {
1210		if re_emus.MatchString(d.Name) {
1211			continue
1212		}
1213		jd := junk.New()
1214		jd["mediaType"] = d.Media
1215		jd["name"] = d.Name
1216		jd["summary"] = html.EscapeString(d.Desc)
1217		jd["type"] = "Document"
1218		jd["url"] = d.URL
1219		atts = append(atts, jd)
1220	}
1221	return atts
1222}
1223
1224// returns activity, object
1225func jonkjonk(user *WhatAbout, h *Honk) (junk.Junk, junk.Junk) {
1226	dt := h.Date.Format(time.RFC3339)
1227	var jo junk.Junk
1228	j := junk.New()
1229	j["id"] = user.URL + "/" + h.What + "/" + shortxid(h.XID)
1230	j["actor"] = user.URL
1231	j["published"] = dt
1232	j["to"] = h.Audience[0]
1233	if len(h.Audience) > 1 {
1234		j["cc"] = h.Audience[1:]
1235	}
1236
1237	switch h.What {
1238	case "update":
1239		fallthrough
1240	case "event":
1241		fallthrough
1242	case "honk":
1243		j["type"] = "Create"
1244		jo = junk.New()
1245		jo["id"] = h.XID
1246		jo["type"] = "Note"
1247		if h.What == "event" {
1248			jo["type"] = "Event"
1249		}
1250		if h.What == "update" {
1251			j["type"] = "Update"
1252			jo["updated"] = dt
1253		}
1254		jo["published"] = dt
1255		jo["url"] = h.XID
1256		jo["attributedTo"] = user.URL
1257		if h.RID != "" {
1258			jo["inReplyTo"] = h.RID
1259		}
1260		if h.Convoy != "" {
1261			jo["context"] = h.Convoy
1262			jo["conversation"] = h.Convoy
1263		}
1264		jo["to"] = h.Audience[0]
1265		if len(h.Audience) > 1 {
1266			jo["cc"] = h.Audience[1:]
1267		}
1268		if !h.Public {
1269			jo["directMessage"] = true
1270		}
1271		h.Noise = re_retag.ReplaceAllString(h.Noise, "")
1272		translate(h)
1273		redoimages(h)
1274		if h.Precis != "" {
1275			jo["sensitive"] = true
1276		}
1277
1278		var replies []string
1279		for _, reply := range h.Replies {
1280			replies = append(replies, reply.XID)
1281		}
1282		if len(replies) > 0 {
1283			jr := junk.New()
1284			jr["type"] = "Collection"
1285			jr["totalItems"] = len(replies)
1286			jr["items"] = replies
1287			jo["replies"] = jr
1288		}
1289
1290		var tags []junk.Junk
1291		for _, m := range h.Mentions {
1292			t := junk.New()
1293			t["type"] = "Mention"
1294			t["name"] = m.Who
1295			t["href"] = m.Where
1296			tags = append(tags, t)
1297		}
1298		for _, o := range h.Onts {
1299			t := junk.New()
1300			t["type"] = "Hashtag"
1301			o = strings.ToLower(o)
1302			t["href"] = fmt.Sprintf("https://%s/o/%s", serverName, o[1:])
1303			t["name"] = o
1304			tags = append(tags, t)
1305		}
1306		for _, e := range herdofemus(h.Noise) {
1307			t := junk.New()
1308			t["id"] = e.ID
1309			t["type"] = "Emoji"
1310			t["name"] = e.Name
1311			i := junk.New()
1312			i["type"] = "Image"
1313			i["mediaType"] = e.Type
1314			i["url"] = e.ID
1315			t["icon"] = i
1316			tags = append(tags, t)
1317		}
1318		for _, e := range fixupflags(h) {
1319			t := junk.New()
1320			t["id"] = e.ID
1321			t["type"] = "Emoji"
1322			t["name"] = e.Name
1323			i := junk.New()
1324			i["type"] = "Image"
1325			i["mediaType"] = "image/png"
1326			i["url"] = e.ID
1327			t["icon"] = i
1328			tags = append(tags, t)
1329		}
1330		if len(tags) > 0 {
1331			jo["tag"] = tags
1332		}
1333		if p := h.Place; p != nil {
1334			t := junk.New()
1335			t["type"] = "Place"
1336			if p.Name != "" {
1337				t["name"] = p.Name
1338			}
1339			if p.Latitude != 0 {
1340				t["latitude"] = p.Latitude
1341			}
1342			if p.Longitude != 0 {
1343				t["longitude"] = p.Longitude
1344			}
1345			if p.Url != "" {
1346				t["url"] = p.Url
1347			}
1348			jo["location"] = t
1349		}
1350		if t := h.Time; t != nil {
1351			jo["startTime"] = t.StartTime.Format(time.RFC3339)
1352			if t.Duration != 0 {
1353				jo["duration"] = "PT" + strings.ToUpper(t.Duration.String())
1354			}
1355		}
1356		atts := activatedonks(h.Donks)
1357		if len(atts) > 0 {
1358			jo["attachment"] = atts
1359		}
1360		jo["summary"] = h.Precis
1361		jo["content"] = h.Noise
1362		j["object"] = jo
1363	case "bonk":
1364		j["type"] = "Announce"
1365		if h.Convoy != "" {
1366			j["context"] = h.Convoy
1367		}
1368		j["object"] = h.XID
1369	case "unbonk":
1370		b := junk.New()
1371		b["id"] = user.URL + "/" + "bonk" + "/" + shortxid(h.XID)
1372		b["type"] = "Announce"
1373		b["actor"] = user.URL
1374		if h.Convoy != "" {
1375			b["context"] = h.Convoy
1376		}
1377		b["object"] = h.XID
1378		j["type"] = "Undo"
1379		j["object"] = b
1380	case "zonk":
1381		j["type"] = "Delete"
1382		j["object"] = h.XID
1383	case "ack":
1384		j["type"] = "Read"
1385		j["object"] = h.XID
1386		if h.Convoy != "" {
1387			j["context"] = h.Convoy
1388		}
1389	case "react":
1390		j["type"] = "EmojiReact"
1391		j["object"] = h.XID
1392		if h.Convoy != "" {
1393			j["context"] = h.Convoy
1394		}
1395		j["content"] = h.Noise
1396	case "deack":
1397		b := junk.New()
1398		b["id"] = user.URL + "/" + "ack" + "/" + shortxid(h.XID)
1399		b["type"] = "Read"
1400		b["actor"] = user.URL
1401		b["object"] = h.XID
1402		if h.Convoy != "" {
1403			b["context"] = h.Convoy
1404		}
1405		j["type"] = "Undo"
1406		j["object"] = b
1407	}
1408
1409	return j, jo
1410}
1411
1412var oldjonks = cache.New(cache.Options{Filler: func(xid string) ([]byte, bool) {
1413	row := stmtAnyXonk.QueryRow(xid)
1414	honk := scanhonk(row)
1415	if honk == nil || !honk.Public {
1416		return nil, true
1417	}
1418	user, _ := butwhatabout(honk.Username)
1419	rawhonks := gethonksbyconvoy(honk.UserID, honk.Convoy, 0)
1420	reversehonks(rawhonks)
1421	for _, h := range rawhonks {
1422		if h.RID == honk.XID && h.Public && (h.Whofore == 2 || h.IsAcked()) {
1423			honk.Replies = append(honk.Replies, h)
1424		}
1425	}
1426	donksforhonks([]*Honk{honk})
1427	_, j := jonkjonk(user, honk)
1428	if j == nil {
1429		elog.Fatalf("what just happened? %v", honk)
1430	}
1431	j["@context"] = itiswhatitis
1432
1433	return j.ToBytes(), true
1434}, Limit: 128})
1435
1436func gimmejonk(xid string) ([]byte, bool) {
1437	var j []byte
1438	ok := oldjonks.Get(xid, &j)
1439	return j, ok
1440}
1441
1442func boxuprcpts(user *WhatAbout, addresses []string, useshared bool) map[string]bool {
1443	rcpts := make(map[string]bool)
1444	for _, a := range addresses {
1445		if a == "" || a == thewholeworld || a == user.URL || strings.HasSuffix(a, "/followers") {
1446			continue
1447		}
1448		if a[0] == '%' {
1449			rcpts[a] = true
1450			continue
1451		}
1452		var box *Box
1453		ok := boxofboxes.Get(a, &box)
1454		if ok && useshared && box.Shared != "" {
1455			rcpts["%"+box.Shared] = true
1456		} else {
1457			rcpts[a] = true
1458		}
1459	}
1460	return rcpts
1461}
1462
1463func chonkifymsg(user *WhatAbout, ch *Chonk) []byte {
1464	dt := ch.Date.Format(time.RFC3339)
1465	aud := []string{ch.Target}
1466
1467	jo := junk.New()
1468	jo["id"] = ch.XID
1469	jo["type"] = "ChatMessage"
1470	jo["published"] = dt
1471	jo["attributedTo"] = user.URL
1472	jo["to"] = aud
1473	jo["content"] = ch.HTML
1474	atts := activatedonks(ch.Donks)
1475	if len(atts) > 0 {
1476		jo["attachment"] = atts
1477	}
1478	var tags []junk.Junk
1479	for _, e := range herdofemus(ch.Noise) {
1480		t := junk.New()
1481		t["id"] = e.ID
1482		t["type"] = "Emoji"
1483		t["name"] = e.Name
1484		i := junk.New()
1485		i["type"] = "Image"
1486		i["mediaType"] = e.Type
1487		i["url"] = e.ID
1488		t["icon"] = i
1489		tags = append(tags, t)
1490	}
1491	if len(tags) > 0 {
1492		jo["tag"] = tags
1493	}
1494
1495	j := junk.New()
1496	j["@context"] = itiswhatitis
1497	j["id"] = user.URL + "/" + "honk" + "/" + shortxid(ch.XID)
1498	j["type"] = "Create"
1499	j["actor"] = user.URL
1500	j["published"] = dt
1501	j["to"] = aud
1502	j["object"] = jo
1503
1504	return j.ToBytes()
1505}
1506
1507func sendchonk(user *WhatAbout, ch *Chonk) {
1508	msg := chonkifymsg(user, ch)
1509
1510	rcpts := make(map[string]bool)
1511	rcpts[ch.Target] = true
1512	for a := range rcpts {
1513		go deliverate(user.ID, a, msg)
1514	}
1515}
1516
1517func honkworldwide(user *WhatAbout, honk *Honk) {
1518	jonk, _ := jonkjonk(user, honk)
1519	jonk["@context"] = itiswhatitis
1520	msg := jonk.ToBytes()
1521
1522	rcpts := boxuprcpts(user, honk.Audience, honk.Public)
1523
1524	if honk.Public {
1525		for _, h := range getdubs(user.ID) {
1526			if h.XID == user.URL {
1527				continue
1528			}
1529			var box *Box
1530			ok := boxofboxes.Get(h.XID, &box)
1531			if ok && box.Shared != "" {
1532				rcpts["%"+box.Shared] = true
1533			} else {
1534				rcpts[h.XID] = true
1535			}
1536		}
1537		for _, f := range getbacktracks(honk.XID) {
1538			if f[0] == '%' {
1539				rcpts[f] = true
1540			} else {
1541				var box *Box
1542				ok := boxofboxes.Get(f, &box)
1543				if ok && box.Shared != "" {
1544					rcpts["%"+box.Shared] = true
1545				} else {
1546					rcpts[f] = true
1547				}
1548			}
1549		}
1550	}
1551	for a := range rcpts {
1552		go deliverate(user.ID, a, msg)
1553	}
1554	if honk.Public && len(honk.Onts) > 0 {
1555		collectiveaction(honk)
1556	}
1557}
1558
1559func collectiveaction(honk *Honk) {
1560	user := getserveruser()
1561	for _, ont := range honk.Onts {
1562		dubs := getnameddubs(readyLuserOne, ont)
1563		if len(dubs) == 0 {
1564			continue
1565		}
1566		j := junk.New()
1567		j["@context"] = itiswhatitis
1568		j["type"] = "Add"
1569		j["id"] = user.URL + "/add/" + shortxid(ont+honk.XID)
1570		j["actor"] = user.URL
1571		j["object"] = honk.XID
1572		j["target"] = fmt.Sprintf("https://%s/o/%s", serverName, ont[1:])
1573		rcpts := make(map[string]bool)
1574		for _, dub := range dubs {
1575			var box *Box
1576			ok := boxofboxes.Get(dub.XID, &box)
1577			if ok && box.Shared != "" {
1578				rcpts["%"+box.Shared] = true
1579			} else {
1580				rcpts[dub.XID] = true
1581			}
1582		}
1583		msg := j.ToBytes()
1584		for a := range rcpts {
1585			go deliverate(user.ID, a, msg)
1586		}
1587	}
1588}
1589
1590func junkuser(user *WhatAbout) junk.Junk {
1591	j := junk.New()
1592	j["@context"] = itiswhatitis
1593	j["id"] = user.URL
1594	j["inbox"] = user.URL + "/inbox"
1595	j["outbox"] = user.URL + "/outbox"
1596	j["name"] = user.Display
1597	j["preferredUsername"] = user.Name
1598	j["summary"] = user.HTAbout
1599	var tags []junk.Junk
1600	for _, o := range user.Onts {
1601		t := junk.New()
1602		t["type"] = "Hashtag"
1603		o = strings.ToLower(o)
1604		t["href"] = fmt.Sprintf("https://%s/o/%s", serverName, o[1:])
1605		t["name"] = o
1606		tags = append(tags, t)
1607	}
1608	if len(tags) > 0 {
1609		j["tag"] = tags
1610	}
1611
1612	if user.ID > 0 {
1613		j["type"] = "Person"
1614		j["url"] = user.URL
1615		j["followers"] = user.URL + "/followers"
1616		j["following"] = user.URL + "/following"
1617		a := junk.New()
1618		a["type"] = "Image"
1619		a["mediaType"] = "image/png"
1620		a["url"] = avatarURL(user)
1621		j["icon"] = a
1622		if ban := user.Options.Banner; ban != "" {
1623			a := junk.New()
1624			a["type"] = "Image"
1625			a["mediaType"] = "image/jpg"
1626			a["url"] = ban
1627			j["image"] = a
1628		}
1629	} else {
1630		j["type"] = "Service"
1631	}
1632	k := junk.New()
1633	k["id"] = user.URL + "#key"
1634	k["owner"] = user.URL
1635	k["publicKeyPem"] = user.Key
1636	j["publicKey"] = k
1637
1638	return j
1639}
1640
1641var oldjonkers = cache.New(cache.Options{Filler: func(name string) ([]byte, bool) {
1642	user, err := butwhatabout(name)
1643	if err != nil {
1644		return nil, false
1645	}
1646	j := junkuser(user)
1647	return j.ToBytes(), true
1648}, Duration: 1 * time.Minute})
1649
1650func asjonker(name string) ([]byte, bool) {
1651	var j []byte
1652	ok := oldjonkers.Get(name, &j)
1653	return j, ok
1654}
1655
1656var handfull = cache.New(cache.Options{Filler: func(name string) (string, bool) {
1657	m := strings.Split(name, "@")
1658	if len(m) != 2 {
1659		dlog.Printf("bad fish name: %s", name)
1660		return "", true
1661	}
1662	var href string
1663	row := stmtGetXonker.QueryRow(name, "fishname")
1664	err := row.Scan(&href)
1665	if err == nil {
1666		return href, true
1667	}
1668	dlog.Printf("fishing for %s", name)
1669	j, err := GetJunkFast(readyLuserOne, fmt.Sprintf("https://%s/.well-known/webfinger?resource=acct:%s", m[1], name))
1670	if err != nil {
1671		ilog.Printf("failed to go fish %s: %s", name, err)
1672		return "", true
1673	}
1674	links, _ := j.GetArray("links")
1675	for _, li := range links {
1676		l, ok := li.(junk.Junk)
1677		if !ok {
1678			continue
1679		}
1680		href, _ := l.GetString("href")
1681		rel, _ := l.GetString("rel")
1682		t, _ := l.GetString("type")
1683		if rel == "self" && friendorfoe(t) {
1684			when := time.Now().UTC().Format(dbtimeformat)
1685			_, err := stmtSaveXonker.Exec(name, href, "fishname", when)
1686			if err != nil {
1687				elog.Printf("error saving fishname: %s", err)
1688			}
1689			return href, true
1690		}
1691	}
1692	return href, true
1693}, Duration: 1 * time.Minute})
1694
1695func gofish(name string) string {
1696	if name[0] == '@' {
1697		name = name[1:]
1698	}
1699	var href string
1700	handfull.Get(name, &href)
1701	return href
1702}
1703
1704func investigate(name string) (*SomeThing, error) {
1705	if name == "" {
1706		return nil, fmt.Errorf("no name")
1707	}
1708	if name[0] == '@' {
1709		name = gofish(name)
1710	}
1711	if name == "" {
1712		return nil, fmt.Errorf("no name")
1713	}
1714	obj, err := GetJunkFast(readyLuserOne, name)
1715	if err != nil {
1716		return nil, err
1717	}
1718	allinjest(originate(name), obj)
1719	return somethingabout(obj)
1720}
1721
1722func somethingabout(obj junk.Junk) (*SomeThing, error) {
1723	info := new(SomeThing)
1724	t, _ := obj.GetString("type")
1725	isowned := false
1726	switch t {
1727	case "Person":
1728		fallthrough
1729	case "Group":
1730		fallthrough
1731	case "Organization":
1732		fallthrough
1733	case "Application":
1734		fallthrough
1735	case "Service":
1736		info.What = SomeActor
1737	case "OrderedCollection":
1738		isowned = true
1739		fallthrough
1740	case "Collection":
1741		info.What = SomeCollection
1742	default:
1743		return nil, fmt.Errorf("unknown object type")
1744	}
1745	info.XID, _ = obj.GetString("id")
1746	info.Name, _ = obj.GetString("preferredUsername")
1747	if info.Name == "" {
1748		info.Name, _ = obj.GetString("name")
1749	}
1750	if isowned {
1751		info.Owner, _ = obj.GetString("attributedTo")
1752	}
1753	if info.Owner == "" {
1754		info.Owner = info.XID
1755	}
1756	return info, nil
1757}
1758
1759func allinjest(origin string, obj junk.Junk) {
1760	keyobj, ok := obj.GetMap("publicKey")
1761	if ok {
1762		ingestpubkey(origin, keyobj)
1763	}
1764	ingestboxes(origin, obj)
1765	ingesthandle(origin, obj)
1766}
1767
1768func ingestpubkey(origin string, obj junk.Junk) {
1769	keyobj, ok := obj.GetMap("publicKey")
1770	if ok {
1771		obj = keyobj
1772	}
1773	keyname, ok := obj.GetString("id")
1774	var data string
1775	row := stmtGetXonker.QueryRow(keyname, "pubkey")
1776	err := row.Scan(&data)
1777	if err == nil {
1778		return
1779	}
1780	if !ok || origin != originate(keyname) {
1781		ilog.Printf("bad key origin %s <> %s", origin, keyname)
1782		return
1783	}
1784	dlog.Printf("ingesting a needed pubkey: %s", keyname)
1785	owner, ok := obj.GetString("owner")
1786	if !ok {
1787		ilog.Printf("error finding %s pubkey owner", keyname)
1788		return
1789	}
1790	data, ok = obj.GetString("publicKeyPem")
1791	if !ok {
1792		ilog.Printf("error finding %s pubkey", keyname)
1793		return
1794	}
1795	if originate(owner) != origin {
1796		ilog.Printf("bad key owner: %s <> %s", owner, origin)
1797		return
1798	}
1799	_, _, err = httpsig.DecodeKey(data)
1800	if err != nil {
1801		ilog.Printf("error decoding %s pubkey: %s", keyname, err)
1802		return
1803	}
1804	when := time.Now().UTC().Format(dbtimeformat)
1805	_, err = stmtSaveXonker.Exec(keyname, data, "pubkey", when)
1806	if err != nil {
1807		elog.Printf("error saving key: %s", err)
1808	}
1809}
1810
1811func ingestboxes(origin string, obj junk.Junk) {
1812	ident, _ := obj.GetString("id")
1813	if ident == "" {
1814		return
1815	}
1816	if originate(ident) != origin {
1817		return
1818	}
1819	var info string
1820	row := stmtGetXonker.QueryRow(ident, "boxes")
1821	err := row.Scan(&info)
1822	if err == nil {
1823		return
1824	}
1825	dlog.Printf("ingesting boxes: %s", ident)
1826	inbox, _ := obj.GetString("inbox")
1827	outbox, _ := obj.GetString("outbox")
1828	sbox, _ := obj.GetString("endpoints", "sharedInbox")
1829	if inbox != "" {
1830		when := time.Now().UTC().Format(dbtimeformat)
1831		m := strings.Join([]string{inbox, outbox, sbox}, " ")
1832		_, err = stmtSaveXonker.Exec(ident, m, "boxes", when)
1833		if err != nil {
1834			elog.Printf("error saving boxes: %s", err)
1835		}
1836	}
1837}
1838
1839func ingesthandle(origin string, obj junk.Junk) {
1840	xid, _ := obj.GetString("id")
1841	if xid == "" {
1842		return
1843	}
1844	if originate(xid) != origin {
1845		return
1846	}
1847	var handle string
1848	row := stmtGetXonker.QueryRow(xid, "handle")
1849	err := row.Scan(&handle)
1850	if err == nil {
1851		return
1852	}
1853	handle, _ = obj.GetString("preferredUsername")
1854	if handle != "" {
1855		when := time.Now().UTC().Format(dbtimeformat)
1856		_, err = stmtSaveXonker.Exec(xid, handle, "handle", when)
1857		if err != nil {
1858			elog.Printf("error saving handle: %s", err)
1859		}
1860	}
1861}
1862
1863func updateMe(username string) {
1864	var user *WhatAbout
1865	somenamedusers.Get(username, &user)
1866	dt := time.Now().UTC().Format(time.RFC3339)
1867	j := junk.New()
1868	j["@context"] = itiswhatitis
1869	j["id"] = fmt.Sprintf("%s/upme/%s/%d", user.URL, user.Name, time.Now().Unix())
1870	j["actor"] = user.URL
1871	j["published"] = dt
1872	j["to"] = thewholeworld
1873	j["type"] = "Update"
1874	j["object"] = junkuser(user)
1875
1876	msg := j.ToBytes()
1877
1878	rcpts := make(map[string]bool)
1879	for _, f := range getdubs(user.ID) {
1880		if f.XID == user.URL {
1881			continue
1882		}
1883		var box *Box
1884		boxofboxes.Get(f.XID, &box)
1885		if box != nil && box.Shared != "" {
1886			rcpts["%"+box.Shared] = true
1887		} else {
1888			rcpts[f.XID] = true
1889		}
1890	}
1891	for a := range rcpts {
1892		go deliverate(user.ID, a, msg)
1893	}
1894}
1895
1896func followme(user *WhatAbout, who string, name string, j junk.Junk) {
1897	folxid, _ := j.GetString("id")
1898
1899	ilog.Printf("updating honker follow: %s %s", who, folxid)
1900
1901	var x string
1902	db := opendatabase()
1903	row := db.QueryRow("select xid from honkers where name = ? and xid = ? and userid = ? and flavor in ('dub', 'undub')", name, who, user.ID)
1904	err := row.Scan(&x)
1905	if err != sql.ErrNoRows {
1906		ilog.Printf("duplicate follow request: %s", who)
1907		_, err = stmtUpdateFlavor.Exec("dub", folxid, user.ID, name, who, "undub")
1908		if err != nil {
1909			elog.Printf("error updating honker: %s", err)
1910		}
1911	} else {
1912		stmtSaveDub.Exec(user.ID, name, who, "dub", folxid)
1913	}
1914	go rubadubdub(user, j)
1915}
1916
1917func unfollowme(user *WhatAbout, who string, name string, j junk.Junk) {
1918	var folxid string
1919	if who == "" {
1920		folxid, _ = j.GetString("object")
1921
1922		db := opendatabase()
1923		row := db.QueryRow("select xid, name from honkers where userid = ? and folxid = ? and flavor in ('dub', 'undub')", user.ID, folxid)
1924		err := row.Scan(&who, &name)
1925		if err != nil {
1926			if err != sql.ErrNoRows {
1927				elog.Printf("error scanning honker: %s", err)
1928			}
1929			return
1930		}
1931	}
1932
1933	ilog.Printf("updating honker undo: %s %s", who, folxid)
1934	_, err := stmtUpdateFlavor.Exec("undub", folxid, user.ID, name, who, "dub")
1935	if err != nil {
1936		elog.Printf("error updating honker: %s", err)
1937		return
1938	}
1939}
1940
1941func followyou(user *WhatAbout, honkerid int64, sync bool) {
1942	var url, owner string
1943	db := opendatabase()
1944	row := db.QueryRow("select xid, owner from honkers where honkerid = ? and userid = ? and flavor in ('unsub', 'peep', 'presub', 'sub')",
1945		honkerid, user.ID)
1946	err := row.Scan(&url, &owner)
1947	if err != nil {
1948		elog.Printf("can't get honker xid: %s", err)
1949		return
1950	}
1951	folxid := xfiltrate()
1952	ilog.Printf("subscribing to %s", url)
1953	_, err = db.Exec("update honkers set flavor = ?, folxid = ? where honkerid = ?", "presub", folxid, honkerid)
1954	if err != nil {
1955		elog.Printf("error updating honker: %s", err)
1956		return
1957	}
1958	if sync {
1959		subsub(user, url, owner, folxid)
1960	} else {
1961		go subsub(user, url, owner, folxid)
1962	}
1963
1964}
1965func unfollowyou(user *WhatAbout, honkerid int64, sync bool) {
1966	db := opendatabase()
1967	row := db.QueryRow("select xid, owner, folxid, flavor from honkers where honkerid = ? and userid = ? and flavor in ('unsub', 'peep', 'presub', 'sub')",
1968		honkerid, user.ID)
1969	var url, owner, folxid, flavor string
1970	err := row.Scan(&url, &owner, &folxid, &flavor)
1971	if err != nil {
1972		elog.Printf("can't get honker xid: %s", err)
1973		return
1974	}
1975	if flavor == "peep" {
1976		return
1977	}
1978	ilog.Printf("unsubscribing from %s", url)
1979	_, err = db.Exec("update honkers set flavor = ? where honkerid = ?", "unsub", honkerid)
1980	if err != nil {
1981		elog.Printf("error updating honker: %s", err)
1982		return
1983	}
1984	if sync {
1985		itakeitallback(user, url, owner, folxid)
1986	} else {
1987		go itakeitallback(user, url, owner, folxid)
1988	}
1989}
1990
1991func followyou2(user *WhatAbout, j junk.Junk) {
1992	who, _ := j.GetString("actor")
1993
1994	ilog.Printf("updating honker accept: %s", who)
1995	db := opendatabase()
1996	row := db.QueryRow("select name, folxid from honkers where userid = ? and xid = ? and flavor in ('presub', 'sub')",
1997		user.ID, who)
1998	var name, folxid string
1999	err := row.Scan(&name, &folxid)
2000	if err != nil {
2001		elog.Printf("can't get honker name: %s", err)
2002		return
2003	}
2004	_, err = stmtUpdateFlavor.Exec("sub", folxid, user.ID, name, who, "presub")
2005	if err != nil {
2006		elog.Printf("error updating honker: %s", err)
2007		return
2008	}
2009}
2010
2011func nofollowyou2(user *WhatAbout, j junk.Junk) {
2012	who, _ := j.GetString("actor")
2013
2014	ilog.Printf("updating honker reject: %s", who)
2015	db := opendatabase()
2016	row := db.QueryRow("select name, folxid from honkers where userid = ? and xid = ? and flavor in ('presub', 'sub')",
2017		user.ID, who)
2018	var name, folxid string
2019	err := row.Scan(&name, &folxid)
2020	if err != nil {
2021		elog.Printf("can't get honker name: %s", err)
2022		return
2023	}
2024	_, err = stmtUpdateFlavor.Exec("unsub", folxid, user.ID, name, who, "presub")
2025	_, err = stmtUpdateFlavor.Exec("unsub", folxid, user.ID, name, who, "sub")
2026	if err != nil {
2027		elog.Printf("error updating honker: %s", err)
2028		return
2029	}
2030}