all repos — honk @ d819c1ad5d4d42d5dc3b43edc433fbc0feaf1c22

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