all repos — honk @ 9699cb0a2769604238f6b3b636145ea28f9fe339

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	"net/url"
  29	"os"
  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(serverUID, 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
 509func xonksaver(user *WhatAbout, item junk.Junk, origin string) *Honk {
 510	depth := 0
 511	maxdepth := 10
 512	currenttid := ""
 513	goingup := 0
 514	var xonkxonkfn func(item junk.Junk, origin string, isUpdate bool) *Honk
 515
 516	saveonemore := func(xid string) {
 517		dlog.Printf("getting onemore: %s", xid)
 518		if depth >= maxdepth {
 519			ilog.Printf("in too deep")
 520			return
 521		}
 522		obj, err := GetJunkHardMode(user.ID, xid)
 523		if err != nil {
 524			ilog.Printf("error getting onemore: %s: %s", xid, err)
 525			return
 526		}
 527		depth++
 528		xonkxonkfn(obj, originate(xid), false)
 529		depth--
 530	}
 531
 532	xonkxonkfn = func(item junk.Junk, origin string, isUpdate bool) *Honk {
 533		id, _ := item.GetString("id")
 534		what := firstofmany(item, "type")
 535		dt, ok := item.GetString("published")
 536		if !ok {
 537			dt = time.Now().Format(time.RFC3339)
 538		}
 539
 540		var err error
 541		var xid, rid, url, convoy string
 542		var replies []string
 543		var obj junk.Junk
 544		switch what {
 545		case "Delete":
 546			obj, ok = item.GetMap("object")
 547			if ok {
 548				xid, _ = obj.GetString("id")
 549			} else {
 550				xid, _ = item.GetString("object")
 551			}
 552			if xid == "" {
 553				return nil
 554			}
 555			if originate(xid) != origin {
 556				ilog.Printf("forged delete: %s", xid)
 557				return nil
 558			}
 559			ilog.Printf("eradicating %s", xid)
 560			eradicatexonk(user.ID, xid)
 561			return nil
 562		case "Remove":
 563			xid, _ = item.GetString("object")
 564			targ, _ := obj.GetString("target")
 565			ilog.Printf("remove %s from %s", obj, targ)
 566			return nil
 567		case "Tombstone":
 568			xid, _ = item.GetString("id")
 569			if xid == "" {
 570				return nil
 571			}
 572			if originate(xid) != origin {
 573				ilog.Printf("forged delete: %s", xid)
 574				return nil
 575			}
 576			ilog.Printf("eradicating %s", xid)
 577			eradicatexonk(user.ID, xid)
 578			return nil
 579		case "Announce":
 580			obj, ok = item.GetMap("object")
 581			if ok {
 582				xid, _ = obj.GetString("id")
 583			} else {
 584				xid, _ = item.GetString("object")
 585			}
 586			if !needbonkid(user, xid) {
 587				return nil
 588			}
 589			dlog.Printf("getting bonk: %s", xid)
 590			obj, err = GetJunkHardMode(user.ID, xid)
 591			if err != nil {
 592				ilog.Printf("error getting bonk: %s: %s", xid, err)
 593			}
 594			origin = originate(xid)
 595			what = "bonk"
 596		case "Update":
 597			isUpdate = true
 598			fallthrough
 599		case "Create":
 600			obj, ok = item.GetMap("object")
 601			if !ok {
 602				xid, _ = item.GetString("object")
 603				dlog.Printf("getting created honk: %s", xid)
 604				if originate(xid) != origin {
 605					ilog.Printf("out of bounds %s not from %s", xid, origin)
 606					return nil
 607				}
 608				obj, err = GetJunkHardMode(user.ID, xid)
 609				if err != nil {
 610					ilog.Printf("error getting creation: %s", err)
 611				}
 612			}
 613			if obj == nil {
 614				ilog.Printf("no object for creation %s", id)
 615				return nil
 616			}
 617			return xonkxonkfn(obj, origin, isUpdate)
 618		case "Read":
 619			xid, ok = item.GetString("object")
 620			if ok {
 621				if !needxonkid(user, xid) {
 622					dlog.Printf("don't need read obj: %s", xid)
 623					return nil
 624				}
 625				obj, err = GetJunkHardMode(user.ID, xid)
 626				if err != nil {
 627					ilog.Printf("error getting read: %s", err)
 628					return nil
 629				}
 630				return xonkxonkfn(obj, originate(xid), false)
 631			}
 632			return nil
 633		case "Add":
 634			xid, ok = item.GetString("object")
 635			if ok {
 636				// check target...
 637				if !needxonkid(user, xid) {
 638					dlog.Printf("don't need added obj: %s", xid)
 639					return nil
 640				}
 641				obj, err = GetJunkHardMode(user.ID, xid)
 642				if err != nil {
 643					ilog.Printf("error getting add: %s", err)
 644					return nil
 645				}
 646				return xonkxonkfn(obj, originate(xid), false)
 647			}
 648			return nil
 649		case "Move":
 650			obj = item
 651			what = "move"
 652		case "GuessWord": // dealt with below
 653			fallthrough
 654		case "Audio":
 655			fallthrough
 656		case "Image":
 657			fallthrough
 658		case "Video":
 659			fallthrough
 660		case "Question":
 661			fallthrough
 662		case "Note":
 663			fallthrough
 664		case "Article":
 665			fallthrough
 666		case "Page":
 667			obj = item
 668			what = "honk"
 669		case "Event":
 670			obj = item
 671			what = "event"
 672		case "ChatMessage":
 673			obj = item
 674			what = "chonk"
 675		default:
 676			ilog.Printf("unknown activity: %s", what)
 677			dumpactivity(item)
 678			return nil
 679		}
 680
 681		if obj != nil {
 682			xid, _ = obj.GetString("id")
 683		}
 684
 685		if xid == "" {
 686			ilog.Printf("don't know what xid is")
 687			item.Write(ilog.Writer())
 688			return nil
 689		}
 690		if originate(xid) != origin {
 691			ilog.Printf("original sin: %s not from %s", xid, origin)
 692			item.Write(ilog.Writer())
 693			return nil
 694		}
 695
 696		var xonk Honk
 697		// early init
 698		xonk.XID = xid
 699		xonk.UserID = user.ID
 700		xonk.Honker, _ = item.GetString("actor")
 701		if xonk.Honker == "" {
 702			xonk.Honker, _ = item.GetString("attributedTo")
 703		}
 704		if obj != nil {
 705			if xonk.Honker == "" {
 706				xonk.Honker = extractattrto(obj)
 707			}
 708			xonk.Oonker = extractattrto(obj)
 709			if xonk.Oonker == xonk.Honker {
 710				xonk.Oonker = ""
 711			}
 712			xonk.Audience = newphone(nil, obj)
 713		}
 714		xonk.Audience = append(xonk.Audience, xonk.Honker)
 715		xonk.Audience = oneofakind(xonk.Audience)
 716		xonk.Public = loudandproud(xonk.Audience)
 717
 718		var mentions []Mention
 719		if obj != nil {
 720			ot, _ := obj.GetString("type")
 721			url, _ = obj.GetString("url")
 722			if dt2, ok := obj.GetString("published"); ok {
 723				dt = dt2
 724			}
 725			content, _ := obj.GetString("content")
 726			if !strings.HasPrefix(content, "<p>") {
 727				content = "<p>" + content
 728			}
 729			precis, _ := obj.GetString("summary")
 730			if name, ok := obj.GetString("name"); ok {
 731				if precis != "" {
 732					content = precis + "<p>" + content
 733				}
 734				precis = html.EscapeString(name)
 735			}
 736			if sens, _ := obj["sensitive"].(bool); sens && precis == "" {
 737				precis = "unspecified horror"
 738			}
 739			rid, ok = obj.GetString("inReplyTo")
 740			if !ok {
 741				if robj, ok := obj.GetMap("inReplyTo"); ok {
 742					rid, _ = robj.GetString("id")
 743				}
 744			}
 745			convoy, _ = obj.GetString("context")
 746			if convoy == "" {
 747				convoy, _ = obj.GetString("conversation")
 748			}
 749			if ot == "Question" {
 750				if what == "honk" {
 751					what = "qonk"
 752				}
 753				content += "<ul>"
 754				ans, _ := obj.GetArray("oneOf")
 755				for _, ai := range ans {
 756					a, ok := ai.(junk.Junk)
 757					if !ok {
 758						continue
 759					}
 760					as, _ := a.GetString("name")
 761					content += "<li>" + as
 762				}
 763				ans, _ = obj.GetArray("anyOf")
 764				for _, ai := range ans {
 765					a, ok := ai.(junk.Junk)
 766					if !ok {
 767						continue
 768					}
 769					as, _ := a.GetString("name")
 770					content += "<li>" + as
 771				}
 772				content += "</ul>"
 773			}
 774			if ot == "Move" {
 775				targ, _ := obj.GetString("target")
 776				content += string(templates.Sprintf(`<p>Moved to <a href="%s">%s</a>`, targ, targ))
 777			}
 778			if ot == "GuessWord" {
 779				what = "wonk"
 780				content, _ = obj.GetString("content")
 781				xonk.Wonkles, _ = obj.GetString("wordlist")
 782				go savewonkles(xonk.Wonkles)
 783			}
 784			if what == "honk" && rid != "" {
 785				what = "tonk"
 786			}
 787			if len(content) > 90001 {
 788				ilog.Printf("content too long. truncating")
 789				content = content[:90001]
 790			}
 791
 792			xonk.Noise = content
 793			xonk.Precis = precis
 794			if rejectxonk(&xonk) {
 795				dlog.Printf("fast reject: %s", xid)
 796				return nil
 797			}
 798
 799			numatts := 0
 800			procatt := func(att junk.Junk) {
 801				at, _ := att.GetString("type")
 802				mt, _ := att.GetString("mediaType")
 803				u, ok := att.GetString("url")
 804				if !ok {
 805					if ua, ok := att.GetArray("url"); ok && len(ua) > 0 {
 806						u, ok = ua[0].(string)
 807						if !ok {
 808							if uu, ok := ua[0].(junk.Junk); ok {
 809								u, _ = uu.GetString("href")
 810								if mt == "" {
 811									mt, _ = uu.GetString("mediaType")
 812								}
 813							}
 814						}
 815					} else if uu, ok := att.GetMap("url"); ok {
 816						u, _ = uu.GetString("href")
 817						if mt == "" {
 818							mt, _ = uu.GetString("mediaType")
 819						}
 820					}
 821				}
 822				name, _ := att.GetString("name")
 823				desc, _ := att.GetString("summary")
 824				desc = html.UnescapeString(desc)
 825				if desc == "" {
 826					desc = name
 827				}
 828				localize := false
 829				if numatts > 4 {
 830					ilog.Printf("excessive attachment: %s", at)
 831				} else if at == "Document" || at == "Image" {
 832					mt = strings.ToLower(mt)
 833					dlog.Printf("attachment: %s %s", mt, u)
 834					if mt == "text/plain" || mt == "application/pdf" ||
 835						strings.HasPrefix(mt, "image") {
 836						localize = true
 837					}
 838				} else {
 839					ilog.Printf("unknown attachment: %s", at)
 840				}
 841				if skipMedia(&xonk) {
 842					localize = false
 843				}
 844				donk := savedonk(u, name, desc, mt, localize)
 845				if donk != nil {
 846					xonk.Donks = append(xonk.Donks, donk)
 847				}
 848				numatts++
 849			}
 850			atts, _ := obj.GetArray("attachment")
 851			for _, atti := range atts {
 852				att, ok := atti.(junk.Junk)
 853				if !ok {
 854					ilog.Printf("attachment that wasn't map?")
 855					continue
 856				}
 857				procatt(att)
 858			}
 859			if att, ok := obj.GetMap("attachment"); ok {
 860				procatt(att)
 861			}
 862			tags, _ := obj.GetArray("tag")
 863			for _, tagi := range tags {
 864				tag, ok := tagi.(junk.Junk)
 865				if !ok {
 866					continue
 867				}
 868				tt, _ := tag.GetString("type")
 869				name, _ := tag.GetString("name")
 870				desc, _ := tag.GetString("summary")
 871				desc = html.UnescapeString(desc)
 872				if desc == "" {
 873					desc = name
 874				}
 875				if tt == "Emoji" {
 876					icon, _ := tag.GetMap("icon")
 877					mt, _ := icon.GetString("mediaType")
 878					if mt == "" {
 879						mt = "image/png"
 880					}
 881					u, _ := icon.GetString("url")
 882					donk := savedonk(u, name, desc, mt, true)
 883					if donk != nil {
 884						xonk.Donks = append(xonk.Donks, donk)
 885					}
 886				}
 887				if tt == "Hashtag" {
 888					if name == "" || name == "#" {
 889						// skip it
 890					} else {
 891						if name[0] != '#' {
 892							name = "#" + name
 893						}
 894						xonk.Onts = append(xonk.Onts, name)
 895					}
 896				}
 897				if tt == "Place" {
 898					p := new(Place)
 899					p.Name = name
 900					p.Latitude, _ = tag.GetNumber("latitude")
 901					p.Longitude, _ = tag.GetNumber("longitude")
 902					p.Url, _ = tag.GetString("url")
 903					xonk.Place = p
 904				}
 905				if tt == "Mention" {
 906					var m Mention
 907					m.Who, _ = tag.GetString("name")
 908					m.Where, _ = tag.GetString("href")
 909					mentions = append(mentions, m)
 910				}
 911			}
 912			if starttime, ok := obj.GetString("startTime"); ok {
 913				if start, err := time.Parse(time.RFC3339, starttime); err == nil {
 914					t := new(Time)
 915					t.StartTime = start
 916					endtime, _ := obj.GetString("endTime")
 917					t.EndTime, _ = time.Parse(time.RFC3339, endtime)
 918					dura, _ := obj.GetString("duration")
 919					if strings.HasPrefix(dura, "PT") {
 920						dura = strings.ToLower(dura[2:])
 921						d, _ := time.ParseDuration(dura)
 922						t.Duration = Duration(d)
 923					}
 924					xonk.Time = t
 925				}
 926			}
 927			if loca, ok := obj.GetMap("location"); ok {
 928				if tt, _ := loca.GetString("type"); tt == "Place" {
 929					p := new(Place)
 930					p.Name, _ = loca.GetString("name")
 931					p.Latitude, _ = loca.GetNumber("latitude")
 932					p.Longitude, _ = loca.GetNumber("longitude")
 933					p.Url, _ = loca.GetString("url")
 934					xonk.Place = p
 935				}
 936			}
 937
 938			xonk.Onts = oneofakind(xonk.Onts)
 939			replyobj, ok := obj.GetMap("replies")
 940			if ok {
 941				items, ok := replyobj.GetArray("items")
 942				if !ok {
 943					first, ok := replyobj.GetMap("first")
 944					if ok {
 945						items, _ = first.GetArray("items")
 946					}
 947				}
 948				for _, repl := range items {
 949					s, ok := repl.(string)
 950					if ok {
 951						replies = append(replies, s)
 952					}
 953				}
 954			}
 955
 956		}
 957
 958		if currenttid == "" {
 959			currenttid = convoy
 960		}
 961
 962		// init xonk
 963		xonk.What = what
 964		xonk.RID = rid
 965		xonk.Date, _ = time.Parse(time.RFC3339, dt)
 966		xonk.URL = url
 967		xonk.Format = "html"
 968		xonk.Convoy = convoy
 969		xonk.Mentions = mentions
 970		for _, m := range mentions {
 971			if m.Where == user.URL {
 972				xonk.Whofore = 1
 973			}
 974		}
 975		imaginate(&xonk)
 976
 977		if what == "chonk" {
 978			ch := Chonk{
 979				UserID: xonk.UserID,
 980				XID:    xid,
 981				Who:    xonk.Honker,
 982				Target: xonk.Honker,
 983				Date:   xonk.Date,
 984				Noise:  xonk.Noise,
 985				Format: xonk.Format,
 986				Donks:  xonk.Donks,
 987			}
 988			savechonk(&ch)
 989			return nil
 990		}
 991
 992		if isUpdate {
 993			dlog.Printf("something has changed! %s", xonk.XID)
 994			prev := getxonk(user.ID, xonk.XID)
 995			if prev == nil {
 996				ilog.Printf("didn't find old version for update: %s", xonk.XID)
 997				isUpdate = false
 998			} else {
 999				xonk.ID = prev.ID
1000				updatehonk(&xonk)
1001			}
1002		}
1003		if !isUpdate && needxonk(user, &xonk) {
1004			if rid != "" && xonk.Public {
1005				if needxonkid(user, rid) {
1006					goingup++
1007					saveonemore(rid)
1008					goingup--
1009				}
1010				if convoy == "" {
1011					xx := getxonk(user.ID, rid)
1012					if xx != nil {
1013						convoy = xx.Convoy
1014					}
1015				}
1016			}
1017			if convoy == "" {
1018				convoy = currenttid
1019			}
1020			if convoy == "" {
1021				convoy = "data:,missing-" + xfiltrate()
1022				currenttid = convoy
1023			}
1024			xonk.Convoy = convoy
1025			savexonk(&xonk)
1026		}
1027		if goingup == 0 {
1028			for _, replid := range replies {
1029				if needxonkid(user, replid) {
1030					dlog.Printf("missing a reply: %s", replid)
1031					saveonemore(replid)
1032				}
1033			}
1034		}
1035		return &xonk
1036	}
1037
1038	return xonkxonkfn(item, origin, false)
1039}
1040
1041func dumpactivity(item junk.Junk) {
1042	fd, err := os.OpenFile("savedinbox.json", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
1043	if err != nil {
1044		elog.Printf("error opening inbox! %s", err)
1045		return
1046	}
1047	defer fd.Close()
1048	item.Write(fd)
1049	io.WriteString(fd, "\n")
1050}
1051
1052func rubadubdub(user *WhatAbout, req junk.Junk) {
1053	actor, _ := req.GetString("actor")
1054	j := junk.New()
1055	j["@context"] = itiswhatitis
1056	j["id"] = user.URL + "/dub/" + xfiltrate()
1057	j["type"] = "Accept"
1058	j["actor"] = user.URL
1059	j["to"] = actor
1060	j["published"] = time.Now().UTC().Format(time.RFC3339)
1061	j["object"] = req
1062
1063	deliverate(0, user.ID, actor, j.ToBytes(), true)
1064}
1065
1066func itakeitallback(user *WhatAbout, xid string, owner string, folxid string) {
1067	j := junk.New()
1068	j["@context"] = itiswhatitis
1069	j["id"] = user.URL + "/unsub/" + folxid
1070	j["type"] = "Undo"
1071	j["actor"] = user.URL
1072	j["to"] = owner
1073	f := junk.New()
1074	f["id"] = user.URL + "/sub/" + folxid
1075	f["type"] = "Follow"
1076	f["actor"] = user.URL
1077	f["to"] = owner
1078	f["object"] = xid
1079	j["object"] = f
1080	j["published"] = time.Now().UTC().Format(time.RFC3339)
1081
1082	deliverate(0, user.ID, owner, j.ToBytes(), true)
1083}
1084
1085func subsub(user *WhatAbout, xid string, owner string, folxid string) {
1086	if xid == "" {
1087		ilog.Printf("can't subscribe to empty")
1088		return
1089	}
1090	j := junk.New()
1091	j["@context"] = itiswhatitis
1092	j["id"] = user.URL + "/sub/" + folxid
1093	j["type"] = "Follow"
1094	j["actor"] = user.URL
1095	j["to"] = owner
1096	j["object"] = xid
1097	j["published"] = time.Now().UTC().Format(time.RFC3339)
1098
1099	deliverate(0, user.ID, owner, j.ToBytes(), true)
1100}
1101
1102func activatedonks(donks []*Donk) []junk.Junk {
1103	var atts []junk.Junk
1104	for _, d := range donks {
1105		if re_emus.MatchString(d.Name) {
1106			continue
1107		}
1108		jd := junk.New()
1109		jd["mediaType"] = d.Media
1110		jd["name"] = d.Name
1111		jd["summary"] = html.EscapeString(d.Desc)
1112		jd["type"] = "Document"
1113		jd["url"] = d.URL
1114		atts = append(atts, jd)
1115	}
1116	return atts
1117}
1118
1119// returns activity, object
1120func jonkjonk(user *WhatAbout, h *Honk) (junk.Junk, junk.Junk) {
1121	dt := h.Date.Format(time.RFC3339)
1122	var jo junk.Junk
1123	j := junk.New()
1124	j["id"] = user.URL + "/" + h.What + "/" + shortxid(h.XID)
1125	j["actor"] = user.URL
1126	j["published"] = dt
1127	j["to"] = h.Audience[0]
1128	if len(h.Audience) > 1 {
1129		j["cc"] = h.Audience[1:]
1130	}
1131
1132	switch h.What {
1133	case "update":
1134		fallthrough
1135	case "tonk":
1136		fallthrough
1137	case "event":
1138		fallthrough
1139	case "wonk":
1140		fallthrough
1141	case "honk":
1142		j["type"] = "Create"
1143		jo = junk.New()
1144		jo["id"] = h.XID
1145		jo["type"] = "Note"
1146		if h.What == "event" {
1147			jo["type"] = "Event"
1148		} else if h.What == "wonk" {
1149			jo["type"] = "GuessWord"
1150		}
1151		if h.What == "update" {
1152			j["type"] = "Update"
1153			jo["updated"] = dt
1154		}
1155		jo["published"] = dt
1156		jo["url"] = h.XID
1157		jo["attributedTo"] = user.URL
1158		if h.RID != "" {
1159			jo["inReplyTo"] = h.RID
1160		}
1161		if h.Convoy != "" {
1162			jo["context"] = h.Convoy
1163			jo["conversation"] = h.Convoy
1164		}
1165		jo["to"] = h.Audience[0]
1166		if len(h.Audience) > 1 {
1167			jo["cc"] = h.Audience[1:]
1168		}
1169		if !h.Public {
1170			jo["directMessage"] = true
1171		}
1172		translate(h)
1173		redoimages(h)
1174		if h.Precis != "" {
1175			jo["sensitive"] = true
1176		}
1177
1178		var replies []string
1179		for _, reply := range h.Replies {
1180			replies = append(replies, reply.XID)
1181		}
1182		if len(replies) > 0 {
1183			jr := junk.New()
1184			jr["type"] = "Collection"
1185			jr["totalItems"] = len(replies)
1186			jr["items"] = replies
1187			jo["replies"] = jr
1188		}
1189
1190		var tags []junk.Junk
1191		for _, m := range h.Mentions {
1192			t := junk.New()
1193			t["type"] = "Mention"
1194			t["name"] = m.Who
1195			t["href"] = m.Where
1196			tags = append(tags, t)
1197		}
1198		for _, o := range h.Onts {
1199			t := junk.New()
1200			t["type"] = "Hashtag"
1201			o = strings.ToLower(o)
1202			t["href"] = fmt.Sprintf("https://%s/o/%s", serverName, o[1:])
1203			t["name"] = o
1204			tags = append(tags, t)
1205		}
1206		for _, e := range herdofemus(h.Noise) {
1207			t := junk.New()
1208			t["id"] = e.ID
1209			t["type"] = "Emoji"
1210			t["name"] = e.Name
1211			i := junk.New()
1212			i["type"] = "Image"
1213			i["mediaType"] = e.Type
1214			i["url"] = e.ID
1215			t["icon"] = i
1216			tags = append(tags, t)
1217		}
1218		for _, e := range fixupflags(h) {
1219			t := junk.New()
1220			t["id"] = e.ID
1221			t["type"] = "Emoji"
1222			t["name"] = e.Name
1223			i := junk.New()
1224			i["type"] = "Image"
1225			i["mediaType"] = "image/png"
1226			i["url"] = e.ID
1227			t["icon"] = i
1228			tags = append(tags, t)
1229		}
1230		if len(tags) > 0 {
1231			jo["tag"] = tags
1232		}
1233		if p := h.Place; p != nil {
1234			t := junk.New()
1235			t["type"] = "Place"
1236			if p.Name != "" {
1237				t["name"] = p.Name
1238			}
1239			if p.Latitude != 0 {
1240				t["latitude"] = p.Latitude
1241			}
1242			if p.Longitude != 0 {
1243				t["longitude"] = p.Longitude
1244			}
1245			if p.Url != "" {
1246				t["url"] = p.Url
1247			}
1248			jo["location"] = t
1249		}
1250		if t := h.Time; t != nil {
1251			jo["startTime"] = t.StartTime.Format(time.RFC3339)
1252			if t.Duration != 0 {
1253				jo["duration"] = "PT" + strings.ToUpper(t.Duration.String())
1254			}
1255		}
1256		if w := h.Wonkles; w != "" {
1257			jo["wordlist"] = w
1258		}
1259		atts := activatedonks(h.Donks)
1260		if len(atts) > 0 {
1261			jo["attachment"] = atts
1262		}
1263		jo["summary"] = html.EscapeString(h.Precis)
1264		jo["content"] = h.Noise
1265		j["object"] = jo
1266	case "bonk":
1267		j["type"] = "Announce"
1268		if h.Convoy != "" {
1269			j["context"] = h.Convoy
1270		}
1271		j["object"] = h.XID
1272	case "unbonk":
1273		b := junk.New()
1274		b["id"] = user.URL + "/" + "bonk" + "/" + shortxid(h.XID)
1275		b["type"] = "Announce"
1276		b["actor"] = user.URL
1277		if h.Convoy != "" {
1278			b["context"] = h.Convoy
1279		}
1280		b["object"] = h.XID
1281		j["type"] = "Undo"
1282		j["object"] = b
1283	case "zonk":
1284		j["type"] = "Delete"
1285		j["object"] = h.XID
1286	case "ack":
1287		j["type"] = "Read"
1288		j["object"] = h.XID
1289		if h.Convoy != "" {
1290			j["context"] = h.Convoy
1291		}
1292	case "react":
1293		j["type"] = "EmojiReact"
1294		j["object"] = h.XID
1295		if h.Convoy != "" {
1296			j["context"] = h.Convoy
1297		}
1298		j["content"] = h.Noise
1299	case "deack":
1300		b := junk.New()
1301		b["id"] = user.URL + "/" + "ack" + "/" + shortxid(h.XID)
1302		b["type"] = "Read"
1303		b["actor"] = user.URL
1304		b["object"] = h.XID
1305		if h.Convoy != "" {
1306			b["context"] = h.Convoy
1307		}
1308		j["type"] = "Undo"
1309		j["object"] = b
1310	}
1311
1312	return j, jo
1313}
1314
1315var oldjonks = cache.New(cache.Options{Filler: func(xid string) ([]byte, bool) {
1316	row := stmtAnyXonk.QueryRow(xid)
1317	honk := scanhonk(row)
1318	if honk == nil || !honk.Public {
1319		return nil, true
1320	}
1321	user, _ := butwhatabout(honk.Username)
1322	rawhonks := gethonksbyconvoy(honk.UserID, honk.Convoy, 0)
1323	reversehonks(rawhonks)
1324	for _, h := range rawhonks {
1325		if h.RID == honk.XID && h.Public && (h.Whofore == 2 || h.IsAcked()) {
1326			honk.Replies = append(honk.Replies, h)
1327		}
1328	}
1329	donksforhonks([]*Honk{honk})
1330	_, j := jonkjonk(user, honk)
1331	if j == nil {
1332		elog.Fatalf("what just happened? %v", honk)
1333	}
1334	j["@context"] = itiswhatitis
1335
1336	return j.ToBytes(), true
1337}, Limit: 128})
1338
1339func gimmejonk(xid string) ([]byte, bool) {
1340	var j []byte
1341	ok := oldjonks.Get(xid, &j)
1342	return j, ok
1343}
1344
1345func boxuprcpts(user *WhatAbout, addresses []string, useshared bool) map[string]bool {
1346	rcpts := make(map[string]bool)
1347	for _, a := range addresses {
1348		if a == "" || a == thewholeworld || a == user.URL || strings.HasSuffix(a, "/followers") {
1349			continue
1350		}
1351		if a[0] == '%' {
1352			rcpts[a] = true
1353			continue
1354		}
1355		var box *Box
1356		ok := boxofboxes.Get(a, &box)
1357		if ok && useshared && box.Shared != "" {
1358			rcpts["%"+box.Shared] = true
1359		} else {
1360			rcpts[a] = true
1361		}
1362	}
1363	return rcpts
1364}
1365
1366func chonkifymsg(user *WhatAbout, ch *Chonk) []byte {
1367	dt := ch.Date.Format(time.RFC3339)
1368	aud := []string{ch.Target}
1369
1370	jo := junk.New()
1371	jo["id"] = ch.XID
1372	jo["type"] = "ChatMessage"
1373	jo["published"] = dt
1374	jo["attributedTo"] = user.URL
1375	jo["to"] = aud
1376	jo["content"] = ch.HTML
1377	atts := activatedonks(ch.Donks)
1378	if len(atts) > 0 {
1379		jo["attachment"] = atts
1380	}
1381	var tags []junk.Junk
1382	for _, e := range herdofemus(ch.Noise) {
1383		t := junk.New()
1384		t["id"] = e.ID
1385		t["type"] = "Emoji"
1386		t["name"] = e.Name
1387		i := junk.New()
1388		i["type"] = "Image"
1389		i["mediaType"] = e.Type
1390		i["url"] = e.ID
1391		t["icon"] = i
1392		tags = append(tags, t)
1393	}
1394	if len(tags) > 0 {
1395		jo["tag"] = tags
1396	}
1397
1398	j := junk.New()
1399	j["@context"] = itiswhatitis
1400	j["id"] = user.URL + "/" + "honk" + "/" + shortxid(ch.XID)
1401	j["type"] = "Create"
1402	j["actor"] = user.URL
1403	j["published"] = dt
1404	j["to"] = aud
1405	j["object"] = jo
1406
1407	return j.ToBytes()
1408}
1409
1410func sendchonk(user *WhatAbout, ch *Chonk) {
1411	msg := chonkifymsg(user, ch)
1412
1413	rcpts := make(map[string]bool)
1414	rcpts[ch.Target] = true
1415	for a := range rcpts {
1416		go deliverate(0, user.ID, a, msg, true)
1417	}
1418}
1419
1420func honkworldwide(user *WhatAbout, honk *Honk) {
1421	jonk, _ := jonkjonk(user, honk)
1422	jonk["@context"] = itiswhatitis
1423	msg := jonk.ToBytes()
1424
1425	rcpts := boxuprcpts(user, honk.Audience, honk.Public)
1426
1427	if honk.Public {
1428		for _, h := range getdubs(user.ID) {
1429			if h.XID == user.URL {
1430				continue
1431			}
1432			var box *Box
1433			ok := boxofboxes.Get(h.XID, &box)
1434			if ok && box.Shared != "" {
1435				rcpts["%"+box.Shared] = true
1436			} else {
1437				rcpts[h.XID] = true
1438			}
1439		}
1440		for _, f := range getbacktracks(honk.XID) {
1441			if f[0] == '%' {
1442				rcpts[f] = true
1443			} else {
1444				var box *Box
1445				ok := boxofboxes.Get(f, &box)
1446				if ok && box.Shared != "" {
1447					rcpts["%"+box.Shared] = true
1448				} else {
1449					rcpts[f] = true
1450				}
1451			}
1452		}
1453	}
1454	for a := range rcpts {
1455		go deliverate(0, user.ID, a, msg, doesitmatter(honk.What))
1456	}
1457	if honk.Public && len(honk.Onts) > 0 {
1458		collectiveaction(honk)
1459	}
1460}
1461
1462func doesitmatter(what string) bool {
1463	switch what {
1464	case "ack":
1465		return false
1466	case "react":
1467		return false
1468	case "deack":
1469		return false
1470	}
1471	return true
1472}
1473
1474func collectiveaction(honk *Honk) {
1475	user := getserveruser()
1476	for _, ont := range honk.Onts {
1477		dubs := getnameddubs(serverUID, ont)
1478		if len(dubs) == 0 {
1479			continue
1480		}
1481		j := junk.New()
1482		j["@context"] = itiswhatitis
1483		j["type"] = "Add"
1484		j["id"] = user.URL + "/add/" + shortxid(ont+honk.XID)
1485		j["actor"] = user.URL
1486		j["object"] = honk.XID
1487		j["target"] = fmt.Sprintf("https://%s/o/%s", serverName, ont[1:])
1488		rcpts := make(map[string]bool)
1489		for _, dub := range dubs {
1490			var box *Box
1491			ok := boxofboxes.Get(dub.XID, &box)
1492			if ok && box.Shared != "" {
1493				rcpts["%"+box.Shared] = true
1494			} else {
1495				rcpts[dub.XID] = true
1496			}
1497		}
1498		msg := j.ToBytes()
1499		for a := range rcpts {
1500			go deliverate(0, user.ID, a, msg, false)
1501		}
1502	}
1503}
1504
1505func junkuser(user *WhatAbout) junk.Junk {
1506	j := junk.New()
1507	j["@context"] = itiswhatitis
1508	j["id"] = user.URL
1509	j["inbox"] = user.URL + "/inbox"
1510	j["outbox"] = user.URL + "/outbox"
1511	j["name"] = user.Display
1512	j["preferredUsername"] = user.Name
1513	j["summary"] = user.HTAbout
1514	var tags []junk.Junk
1515	for _, o := range user.Onts {
1516		t := junk.New()
1517		t["type"] = "Hashtag"
1518		o = strings.ToLower(o)
1519		t["href"] = fmt.Sprintf("https://%s/o/%s", serverName, o[1:])
1520		t["name"] = o
1521		tags = append(tags, t)
1522	}
1523	if len(tags) > 0 {
1524		j["tag"] = tags
1525	}
1526
1527	if user.ID > 0 {
1528		j["type"] = "Person"
1529		j["url"] = user.URL
1530		j["followers"] = user.URL + "/followers"
1531		j["following"] = user.URL + "/following"
1532		a := junk.New()
1533		a["type"] = "Image"
1534		a["mediaType"] = "image/png"
1535		if ava := user.Options.Avatar; ava != "" {
1536			a["url"] = ava
1537		} else {
1538			u := fmt.Sprintf("https://%s/a?a=%s", serverName, url.QueryEscape(user.URL))
1539			if user.Options.Avahex {
1540				u += "&hex=1"
1541			}
1542			a["url"] = u
1543		}
1544		j["icon"] = a
1545		if ban := user.Options.Banner; ban != "" {
1546			a := junk.New()
1547			a["type"] = "Image"
1548			a["mediaType"] = "image/jpg"
1549			a["url"] = ban
1550			j["image"] = a
1551		}
1552	} else {
1553		j["type"] = "Service"
1554	}
1555	k := junk.New()
1556	k["id"] = user.URL + "#key"
1557	k["owner"] = user.URL
1558	k["publicKeyPem"] = user.Key
1559	j["publicKey"] = k
1560
1561	return j
1562}
1563
1564var oldjonkers = cache.New(cache.Options{Filler: func(name string) ([]byte, bool) {
1565	user, err := butwhatabout(name)
1566	if err != nil {
1567		return nil, false
1568	}
1569	var buf bytes.Buffer
1570	j := junkuser(user)
1571	j.Write(&buf)
1572	return buf.Bytes(), true
1573}, Duration: 1 * time.Minute})
1574
1575func asjonker(name string) ([]byte, bool) {
1576	var j []byte
1577	ok := oldjonkers.Get(name, &j)
1578	return j, ok
1579}
1580
1581var handfull = cache.New(cache.Options{Filler: func(name string) (string, bool) {
1582	m := strings.Split(name, "@")
1583	if len(m) != 2 {
1584		dlog.Printf("bad fish name: %s", name)
1585		return "", true
1586	}
1587	var href string
1588	row := stmtGetXonker.QueryRow(name, "fishname")
1589	err := row.Scan(&href)
1590	if err == nil {
1591		return href, true
1592	}
1593	dlog.Printf("fishing for %s", name)
1594	j, err := GetJunkFast(serverUID, fmt.Sprintf("https://%s/.well-known/webfinger?resource=acct:%s", m[1], name))
1595	if err != nil {
1596		ilog.Printf("failed to go fish %s: %s", name, err)
1597		return "", true
1598	}
1599	links, _ := j.GetArray("links")
1600	for _, li := range links {
1601		l, ok := li.(junk.Junk)
1602		if !ok {
1603			continue
1604		}
1605		href, _ := l.GetString("href")
1606		rel, _ := l.GetString("rel")
1607		t, _ := l.GetString("type")
1608		if rel == "self" && friendorfoe(t) {
1609			when := time.Now().UTC().Format(dbtimeformat)
1610			_, err := stmtSaveXonker.Exec(name, href, "fishname", when)
1611			if err != nil {
1612				elog.Printf("error saving fishname: %s", err)
1613			}
1614			return href, true
1615		}
1616	}
1617	return href, true
1618}, Duration: 1 * time.Minute})
1619
1620func gofish(name string) string {
1621	if name[0] == '@' {
1622		name = name[1:]
1623	}
1624	var href string
1625	handfull.Get(name, &href)
1626	return href
1627}
1628
1629func investigate(name string) (*SomeThing, error) {
1630	if name == "" {
1631		return nil, fmt.Errorf("no name")
1632	}
1633	if name[0] == '@' {
1634		name = gofish(name)
1635	}
1636	if name == "" {
1637		return nil, fmt.Errorf("no name")
1638	}
1639	obj, err := GetJunkFast(serverUID, name)
1640	if err != nil {
1641		return nil, err
1642	}
1643	allinjest(originate(name), obj)
1644	return somethingabout(obj)
1645}
1646
1647func somethingabout(obj junk.Junk) (*SomeThing, error) {
1648	info := new(SomeThing)
1649	t, _ := obj.GetString("type")
1650	switch t {
1651	case "Person":
1652		fallthrough
1653	case "Organization":
1654		fallthrough
1655	case "Application":
1656		fallthrough
1657	case "Service":
1658		info.What = SomeActor
1659	case "OrderedCollection":
1660		fallthrough
1661	case "Collection":
1662		info.What = SomeCollection
1663	default:
1664		return nil, fmt.Errorf("unknown object type")
1665	}
1666	info.XID, _ = obj.GetString("id")
1667	info.Name, _ = obj.GetString("preferredUsername")
1668	if info.Name == "" {
1669		info.Name, _ = obj.GetString("name")
1670	}
1671	info.Owner, _ = obj.GetString("attributedTo")
1672	if info.Owner == "" {
1673		info.Owner = info.XID
1674	}
1675	return info, nil
1676}
1677
1678func allinjest(origin string, obj junk.Junk) {
1679	keyobj, ok := obj.GetMap("publicKey")
1680	if ok {
1681		ingestpubkey(origin, keyobj)
1682	}
1683	ingestboxes(origin, obj)
1684	ingesthandle(origin, obj)
1685}
1686
1687func ingestpubkey(origin string, obj junk.Junk) {
1688	keyobj, ok := obj.GetMap("publicKey")
1689	if ok {
1690		obj = keyobj
1691	}
1692	keyname, ok := obj.GetString("id")
1693	var data string
1694	row := stmtGetXonker.QueryRow(keyname, "pubkey")
1695	err := row.Scan(&data)
1696	if err == nil {
1697		return
1698	}
1699	if !ok || origin != originate(keyname) {
1700		ilog.Printf("bad key origin %s <> %s", origin, keyname)
1701		return
1702	}
1703	dlog.Printf("ingesting a needed pubkey: %s", keyname)
1704	owner, ok := obj.GetString("owner")
1705	if !ok {
1706		ilog.Printf("error finding %s pubkey owner", keyname)
1707		return
1708	}
1709	data, ok = obj.GetString("publicKeyPem")
1710	if !ok {
1711		ilog.Printf("error finding %s pubkey", keyname)
1712		return
1713	}
1714	if originate(owner) != origin {
1715		ilog.Printf("bad key owner: %s <> %s", owner, origin)
1716		return
1717	}
1718	_, _, err = httpsig.DecodeKey(data)
1719	if err != nil {
1720		ilog.Printf("error decoding %s pubkey: %s", keyname, err)
1721		return
1722	}
1723	when := time.Now().UTC().Format(dbtimeformat)
1724	_, err = stmtSaveXonker.Exec(keyname, data, "pubkey", when)
1725	if err != nil {
1726		elog.Printf("error saving key: %s", err)
1727	}
1728}
1729
1730func ingestboxes(origin string, obj junk.Junk) {
1731	ident, _ := obj.GetString("id")
1732	if ident == "" {
1733		return
1734	}
1735	if originate(ident) != origin {
1736		return
1737	}
1738	var info string
1739	row := stmtGetXonker.QueryRow(ident, "boxes")
1740	err := row.Scan(&info)
1741	if err == nil {
1742		return
1743	}
1744	dlog.Printf("ingesting boxes: %s", ident)
1745	inbox, _ := obj.GetString("inbox")
1746	outbox, _ := obj.GetString("outbox")
1747	sbox, _ := obj.GetString("endpoints", "sharedInbox")
1748	if inbox != "" {
1749		when := time.Now().UTC().Format(dbtimeformat)
1750		m := strings.Join([]string{inbox, outbox, sbox}, " ")
1751		_, err = stmtSaveXonker.Exec(ident, m, "boxes", when)
1752		if err != nil {
1753			elog.Printf("error saving boxes: %s", err)
1754		}
1755	}
1756}
1757
1758func ingesthandle(origin string, obj junk.Junk) {
1759	xid, _ := obj.GetString("id")
1760	if xid == "" {
1761		return
1762	}
1763	if originate(xid) != origin {
1764		return
1765	}
1766	var handle string
1767	row := stmtGetXonker.QueryRow(xid, "handle")
1768	err := row.Scan(&handle)
1769	if err == nil {
1770		return
1771	}
1772	handle, _ = obj.GetString("preferredUsername")
1773	if handle != "" {
1774		when := time.Now().UTC().Format(dbtimeformat)
1775		_, err = stmtSaveXonker.Exec(xid, handle, "handle", when)
1776		if err != nil {
1777			elog.Printf("error saving handle: %s", err)
1778		}
1779	}
1780}
1781
1782func updateMe(username string) {
1783	var user *WhatAbout
1784	somenamedusers.Get(username, &user)
1785	dt := time.Now().UTC().Format(time.RFC3339)
1786	j := junk.New()
1787	j["@context"] = itiswhatitis
1788	j["id"] = fmt.Sprintf("%s/upme/%s/%d", user.URL, user.Name, time.Now().Unix())
1789	j["actor"] = user.URL
1790	j["published"] = dt
1791	j["to"] = thewholeworld
1792	j["type"] = "Update"
1793	j["object"] = junkuser(user)
1794
1795	msg := j.ToBytes()
1796
1797	rcpts := make(map[string]bool)
1798	for _, f := range getdubs(user.ID) {
1799		if f.XID == user.URL {
1800			continue
1801		}
1802		var box *Box
1803		boxofboxes.Get(f.XID, &box)
1804		if box != nil && box.Shared != "" {
1805			rcpts["%"+box.Shared] = true
1806		} else {
1807			rcpts[f.XID] = true
1808		}
1809	}
1810	for a := range rcpts {
1811		go deliverate(0, user.ID, a, msg, false)
1812	}
1813}
1814
1815func followme(user *WhatAbout, who string, name string, j junk.Junk) {
1816	folxid, _ := j.GetString("id")
1817
1818	ilog.Printf("updating honker follow: %s %s", who, folxid)
1819
1820	var x string
1821	db := opendatabase()
1822	row := db.QueryRow("select xid from honkers where name = ? and xid = ? and userid = ? and flavor in ('dub', 'undub')", name, who, user.ID)
1823	err := row.Scan(&x)
1824	if err != sql.ErrNoRows {
1825		ilog.Printf("duplicate follow request: %s", who)
1826		_, err = stmtUpdateFlavor.Exec("dub", folxid, user.ID, name, who, "undub")
1827		if err != nil {
1828			elog.Printf("error updating honker: %s", err)
1829		}
1830	} else {
1831		stmtSaveDub.Exec(user.ID, name, who, "dub", folxid)
1832	}
1833	go rubadubdub(user, j)
1834}
1835
1836func unfollowme(user *WhatAbout, who string, name string, j junk.Junk) {
1837	var folxid string
1838	if who == "" {
1839		folxid, _ = j.GetString("object")
1840
1841		db := opendatabase()
1842		row := db.QueryRow("select xid, name from honkers where userid = ? and folxid = ? and flavor in ('dub', 'undub')", user.ID, folxid)
1843		err := row.Scan(&who, &name)
1844		if err != nil {
1845			if err != sql.ErrNoRows {
1846				elog.Printf("error scanning honker: %s", err)
1847			}
1848			return
1849		}
1850	}
1851
1852	ilog.Printf("updating honker undo: %s %s", who, folxid)
1853	_, err := stmtUpdateFlavor.Exec("undub", folxid, user.ID, name, who, "dub")
1854	if err != nil {
1855		elog.Printf("error updating honker: %s", err)
1856		return
1857	}
1858}
1859
1860func followyou(user *WhatAbout, honkerid int64) {
1861	var url, owner string
1862	db := opendatabase()
1863	row := db.QueryRow("select xid, owner from honkers where honkerid = ? and userid = ? and flavor in ('unsub', 'peep', 'presub', 'sub')",
1864		honkerid, user.ID)
1865	err := row.Scan(&url, &owner)
1866	if err != nil {
1867		elog.Printf("can't get honker xid: %s", err)
1868		return
1869	}
1870	folxid := xfiltrate()
1871	ilog.Printf("subscribing to %s", url)
1872	_, err = db.Exec("update honkers set flavor = ?, folxid = ? where honkerid = ?", "presub", folxid, honkerid)
1873	if err != nil {
1874		elog.Printf("error updating honker: %s", err)
1875		return
1876	}
1877	go subsub(user, url, owner, folxid)
1878
1879}
1880func unfollowyou(user *WhatAbout, honkerid int64) {
1881	db := opendatabase()
1882	row := db.QueryRow("select xid, owner, folxid from honkers where honkerid = ? and userid = ? and flavor in ('sub')",
1883		honkerid, user.ID)
1884	var url, owner, folxid string
1885	err := row.Scan(&url, &owner, &folxid)
1886	if err != nil {
1887		elog.Printf("can't get honker xid: %s", err)
1888		return
1889	}
1890	ilog.Printf("unsubscribing from %s", url)
1891	_, err = db.Exec("update honkers set flavor = ? where honkerid = ?", "unsub", honkerid)
1892	if err != nil {
1893		elog.Printf("error updating honker: %s", err)
1894		return
1895	}
1896	go itakeitallback(user, url, owner, folxid)
1897}
1898
1899func followyou2(user *WhatAbout, j junk.Junk) {
1900	who, _ := j.GetString("actor")
1901
1902	ilog.Printf("updating honker accept: %s", who)
1903	db := opendatabase()
1904	row := db.QueryRow("select name, folxid from honkers where userid = ? and xid = ? and flavor in ('presub')",
1905		user.ID, who)
1906	var name, folxid string
1907	err := row.Scan(&name, &folxid)
1908	if err != nil {
1909		elog.Printf("can't get honker name: %s", err)
1910		return
1911	}
1912	_, err = stmtUpdateFlavor.Exec("sub", folxid, user.ID, name, who, "presub")
1913	if err != nil {
1914		elog.Printf("error updating honker: %s", err)
1915		return
1916	}
1917}
1918
1919func nofollowyou2(user *WhatAbout, j junk.Junk) {
1920	who, _ := j.GetString("actor")
1921
1922	ilog.Printf("updating honker reject: %s", who)
1923	db := opendatabase()
1924	row := db.QueryRow("select name, folxid from honkers where userid = ? and xid = ? and flavor in ('presub', 'sub')",
1925		user.ID, who)
1926	var name, folxid string
1927	err := row.Scan(&name, &folxid)
1928	if err != nil {
1929		elog.Printf("can't get honker name: %s", err)
1930		return
1931	}
1932	_, err = stmtUpdateFlavor.Exec("unsub", folxid, user.ID, name, who, "presub")
1933	_, err = stmtUpdateFlavor.Exec("unsub", folxid, user.ID, name, who, "sub")
1934	if err != nil {
1935		elog.Printf("error updating honker: %s", err)
1936		return
1937	}
1938}