all repos — honk @ 08d01fd0986333ea15d1f30f24af8ca72cdafa75

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