all repos — honk @ v1.2.2

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