all repos — honk @ 6edbe6d79aa661edf593d6fda404d231da603687

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 gettings 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	j["@context"] = itiswhatitis
1332
1333	return j.ToBytes(), true
1334}, Limit: 128})
1335
1336func gimmejonk(xid string) ([]byte, bool) {
1337	var j []byte
1338	ok := oldjonks.Get(xid, &j)
1339	return j, ok
1340}
1341
1342func boxuprcpts(user *WhatAbout, addresses []string, useshared bool) map[string]bool {
1343	rcpts := make(map[string]bool)
1344	for _, a := range addresses {
1345		if a == "" || a == thewholeworld || a == user.URL || strings.HasSuffix(a, "/followers") {
1346			continue
1347		}
1348		if a[0] == '%' {
1349			rcpts[a] = true
1350			continue
1351		}
1352		var box *Box
1353		ok := boxofboxes.Get(a, &box)
1354		if ok && useshared && box.Shared != "" {
1355			rcpts["%"+box.Shared] = true
1356		} else {
1357			rcpts[a] = true
1358		}
1359	}
1360	return rcpts
1361}
1362
1363func chonkifymsg(user *WhatAbout, ch *Chonk) []byte {
1364	dt := ch.Date.Format(time.RFC3339)
1365	aud := []string{ch.Target}
1366
1367	jo := junk.New()
1368	jo["id"] = ch.XID
1369	jo["type"] = "ChatMessage"
1370	jo["published"] = dt
1371	jo["attributedTo"] = user.URL
1372	jo["to"] = aud
1373	jo["content"] = ch.HTML
1374	atts := activatedonks(ch.Donks)
1375	if len(atts) > 0 {
1376		jo["attachment"] = atts
1377	}
1378	var tags []junk.Junk
1379	for _, e := range herdofemus(ch.Noise) {
1380		t := junk.New()
1381		t["id"] = e.ID
1382		t["type"] = "Emoji"
1383		t["name"] = e.Name
1384		i := junk.New()
1385		i["type"] = "Image"
1386		i["mediaType"] = e.Type
1387		i["url"] = e.ID
1388		t["icon"] = i
1389		tags = append(tags, t)
1390	}
1391	if len(tags) > 0 {
1392		jo["tag"] = tags
1393	}
1394
1395	j := junk.New()
1396	j["@context"] = itiswhatitis
1397	j["id"] = user.URL + "/" + "honk" + "/" + shortxid(ch.XID)
1398	j["type"] = "Create"
1399	j["actor"] = user.URL
1400	j["published"] = dt
1401	j["to"] = aud
1402	j["object"] = jo
1403
1404	return j.ToBytes()
1405}
1406
1407func sendchonk(user *WhatAbout, ch *Chonk) {
1408	msg := chonkifymsg(user, ch)
1409
1410	rcpts := make(map[string]bool)
1411	rcpts[ch.Target] = true
1412	for a := range rcpts {
1413		go deliverate(0, user.ID, a, msg, true)
1414	}
1415}
1416
1417func honkworldwide(user *WhatAbout, honk *Honk) {
1418	jonk, _ := jonkjonk(user, honk)
1419	jonk["@context"] = itiswhatitis
1420	msg := jonk.ToBytes()
1421
1422	rcpts := boxuprcpts(user, honk.Audience, honk.Public)
1423
1424	if honk.Public {
1425		for _, h := range getdubs(user.ID) {
1426			if h.XID == user.URL {
1427				continue
1428			}
1429			var box *Box
1430			ok := boxofboxes.Get(h.XID, &box)
1431			if ok && box.Shared != "" {
1432				rcpts["%"+box.Shared] = true
1433			} else {
1434				rcpts[h.XID] = true
1435			}
1436		}
1437		for _, f := range getbacktracks(honk.XID) {
1438			if f[0] == '%' {
1439				rcpts[f] = true
1440			} else {
1441				var box *Box
1442				ok := boxofboxes.Get(f, &box)
1443				if ok && box.Shared != "" {
1444					rcpts["%"+box.Shared] = true
1445				} else {
1446					rcpts[f] = true
1447				}
1448			}
1449		}
1450	}
1451	for a := range rcpts {
1452		go deliverate(0, user.ID, a, msg, doesitmatter(honk.What))
1453	}
1454	if honk.Public && len(honk.Onts) > 0 {
1455		collectiveaction(honk)
1456	}
1457}
1458
1459func doesitmatter(what string) bool {
1460	switch what {
1461	case "ack":
1462		return false
1463	case "react":
1464		return false
1465	case "deack":
1466		return false
1467	}
1468	return true
1469}
1470
1471func collectiveaction(honk *Honk) {
1472	user := getserveruser()
1473	for _, ont := range honk.Onts {
1474		dubs := getnameddubs(serverUID, ont)
1475		if len(dubs) == 0 {
1476			continue
1477		}
1478		j := junk.New()
1479		j["@context"] = itiswhatitis
1480		j["type"] = "Add"
1481		j["id"] = user.URL + "/add/" + shortxid(ont+honk.XID)
1482		j["actor"] = user.URL
1483		j["object"] = honk.XID
1484		j["target"] = fmt.Sprintf("https://%s/o/%s", serverName, ont[1:])
1485		rcpts := make(map[string]bool)
1486		for _, dub := range dubs {
1487			var box *Box
1488			ok := boxofboxes.Get(dub.XID, &box)
1489			if ok && box.Shared != "" {
1490				rcpts["%"+box.Shared] = true
1491			} else {
1492				rcpts[dub.XID] = true
1493			}
1494		}
1495		msg := j.ToBytes()
1496		for a := range rcpts {
1497			go deliverate(0, user.ID, a, msg, false)
1498		}
1499	}
1500}
1501
1502func junkuser(user *WhatAbout) junk.Junk {
1503	j := junk.New()
1504	j["@context"] = itiswhatitis
1505	j["id"] = user.URL
1506	j["inbox"] = user.URL + "/inbox"
1507	j["outbox"] = user.URL + "/outbox"
1508	j["name"] = user.Display
1509	j["preferredUsername"] = user.Name
1510	j["summary"] = user.HTAbout
1511	var tags []junk.Junk
1512	for _, o := range user.Onts {
1513		t := junk.New()
1514		t["type"] = "Hashtag"
1515		o = strings.ToLower(o)
1516		t["href"] = fmt.Sprintf("https://%s/o/%s", serverName, o[1:])
1517		t["name"] = o
1518		tags = append(tags, t)
1519	}
1520	if len(tags) > 0 {
1521		j["tag"] = tags
1522	}
1523
1524	if user.ID > 0 {
1525		j["type"] = "Person"
1526		j["url"] = user.URL
1527		j["followers"] = user.URL + "/followers"
1528		j["following"] = user.URL + "/following"
1529		a := junk.New()
1530		a["type"] = "Image"
1531		a["mediaType"] = "image/png"
1532		if ava := user.Options.Avatar; ava != "" {
1533			a["url"] = ava
1534		} else {
1535			u := fmt.Sprintf("https://%s/a?a=%s", serverName, url.QueryEscape(user.URL))
1536			if user.Options.Avahex {
1537				u += "&hex=1"
1538			}
1539			a["url"] = u
1540		}
1541		j["icon"] = a
1542	} else {
1543		j["type"] = "Service"
1544	}
1545	k := junk.New()
1546	k["id"] = user.URL + "#key"
1547	k["owner"] = user.URL
1548	k["publicKeyPem"] = user.Key
1549	j["publicKey"] = k
1550
1551	return j
1552}
1553
1554var oldjonkers = cache.New(cache.Options{Filler: func(name string) ([]byte, bool) {
1555	user, err := butwhatabout(name)
1556	if err != nil {
1557		return nil, false
1558	}
1559	var buf bytes.Buffer
1560	j := junkuser(user)
1561	j.Write(&buf)
1562	return buf.Bytes(), true
1563}, Duration: 1 * time.Minute})
1564
1565func asjonker(name string) ([]byte, bool) {
1566	var j []byte
1567	ok := oldjonkers.Get(name, &j)
1568	return j, ok
1569}
1570
1571var handfull = cache.New(cache.Options{Filler: func(name string) (string, bool) {
1572	m := strings.Split(name, "@")
1573	if len(m) != 2 {
1574		dlog.Printf("bad fish name: %s", name)
1575		return "", true
1576	}
1577	var href string
1578	row := stmtGetXonker.QueryRow(name, "fishname")
1579	err := row.Scan(&href)
1580	if err == nil {
1581		return href, true
1582	}
1583	dlog.Printf("fishing for %s", name)
1584	j, err := GetJunkFast(serverUID, fmt.Sprintf("https://%s/.well-known/webfinger?resource=acct:%s", m[1], name))
1585	if err != nil {
1586		ilog.Printf("failed to go fish %s: %s", name, err)
1587		return "", true
1588	}
1589	links, _ := j.GetArray("links")
1590	for _, li := range links {
1591		l, ok := li.(junk.Junk)
1592		if !ok {
1593			continue
1594		}
1595		href, _ := l.GetString("href")
1596		rel, _ := l.GetString("rel")
1597		t, _ := l.GetString("type")
1598		if rel == "self" && friendorfoe(t) {
1599			when := time.Now().UTC().Format(dbtimeformat)
1600			_, err := stmtSaveXonker.Exec(name, href, "fishname", when)
1601			if err != nil {
1602				elog.Printf("error saving fishname: %s", err)
1603			}
1604			return href, true
1605		}
1606	}
1607	return href, true
1608}, Duration: 1 * time.Minute})
1609
1610func gofish(name string) string {
1611	if name[0] == '@' {
1612		name = name[1:]
1613	}
1614	var href string
1615	handfull.Get(name, &href)
1616	return href
1617}
1618
1619func investigate(name string) (*SomeThing, error) {
1620	if name == "" {
1621		return nil, fmt.Errorf("no name")
1622	}
1623	if name[0] == '@' {
1624		name = gofish(name)
1625	}
1626	if name == "" {
1627		return nil, fmt.Errorf("no name")
1628	}
1629	obj, err := GetJunkFast(serverUID, name)
1630	if err != nil {
1631		return nil, err
1632	}
1633	allinjest(originate(name), obj)
1634	return somethingabout(obj)
1635}
1636
1637func somethingabout(obj junk.Junk) (*SomeThing, error) {
1638	info := new(SomeThing)
1639	t, _ := obj.GetString("type")
1640	switch t {
1641	case "Person":
1642		fallthrough
1643	case "Organization":
1644		fallthrough
1645	case "Application":
1646		fallthrough
1647	case "Service":
1648		info.What = SomeActor
1649	case "OrderedCollection":
1650		fallthrough
1651	case "Collection":
1652		info.What = SomeCollection
1653	default:
1654		return nil, fmt.Errorf("unknown object type")
1655	}
1656	info.XID, _ = obj.GetString("id")
1657	info.Name, _ = obj.GetString("preferredUsername")
1658	if info.Name == "" {
1659		info.Name, _ = obj.GetString("name")
1660	}
1661	info.Owner, _ = obj.GetString("attributedTo")
1662	if info.Owner == "" {
1663		info.Owner = info.XID
1664	}
1665	return info, nil
1666}
1667
1668func allinjest(origin string, obj junk.Junk) {
1669	keyobj, ok := obj.GetMap("publicKey")
1670	if ok {
1671		ingestpubkey(origin, keyobj)
1672	}
1673	ingestboxes(origin, obj)
1674	ingesthandle(origin, obj)
1675}
1676
1677func ingestpubkey(origin string, obj junk.Junk) {
1678	keyobj, ok := obj.GetMap("publicKey")
1679	if ok {
1680		obj = keyobj
1681	}
1682	keyname, ok := obj.GetString("id")
1683	var data string
1684	row := stmtGetXonker.QueryRow(keyname, "pubkey")
1685	err := row.Scan(&data)
1686	if err == nil {
1687		return
1688	}
1689	if !ok || origin != originate(keyname) {
1690		ilog.Printf("bad key origin %s <> %s", origin, keyname)
1691		return
1692	}
1693	dlog.Printf("ingesting a needed pubkey: %s", keyname)
1694	owner, ok := obj.GetString("owner")
1695	if !ok {
1696		ilog.Printf("error finding %s pubkey owner", keyname)
1697		return
1698	}
1699	data, ok = obj.GetString("publicKeyPem")
1700	if !ok {
1701		ilog.Printf("error finding %s pubkey", keyname)
1702		return
1703	}
1704	if originate(owner) != origin {
1705		ilog.Printf("bad key owner: %s <> %s", owner, origin)
1706		return
1707	}
1708	_, _, err = httpsig.DecodeKey(data)
1709	if err != nil {
1710		ilog.Printf("error decoding %s pubkey: %s", keyname, err)
1711		return
1712	}
1713	when := time.Now().UTC().Format(dbtimeformat)
1714	_, err = stmtSaveXonker.Exec(keyname, data, "pubkey", when)
1715	if err != nil {
1716		elog.Printf("error saving key: %s", err)
1717	}
1718}
1719
1720func ingestboxes(origin string, obj junk.Junk) {
1721	ident, _ := obj.GetString("id")
1722	if ident == "" {
1723		return
1724	}
1725	if originate(ident) != origin {
1726		return
1727	}
1728	var info string
1729	row := stmtGetXonker.QueryRow(ident, "boxes")
1730	err := row.Scan(&info)
1731	if err == nil {
1732		return
1733	}
1734	dlog.Printf("ingesting boxes: %s", ident)
1735	inbox, _ := obj.GetString("inbox")
1736	outbox, _ := obj.GetString("outbox")
1737	sbox, _ := obj.GetString("endpoints", "sharedInbox")
1738	if inbox != "" {
1739		when := time.Now().UTC().Format(dbtimeformat)
1740		m := strings.Join([]string{inbox, outbox, sbox}, " ")
1741		_, err = stmtSaveXonker.Exec(ident, m, "boxes", when)
1742		if err != nil {
1743			elog.Printf("error saving boxes: %s", err)
1744		}
1745	}
1746}
1747
1748func ingesthandle(origin string, obj junk.Junk) {
1749	xid, _ := obj.GetString("id")
1750	if xid == "" {
1751		return
1752	}
1753	if originate(xid) != origin {
1754		return
1755	}
1756	var handle string
1757	row := stmtGetXonker.QueryRow(xid, "handle")
1758	err := row.Scan(&handle)
1759	if err == nil {
1760		return
1761	}
1762	handle, _ = obj.GetString("preferredUsername")
1763	if handle != "" {
1764		when := time.Now().UTC().Format(dbtimeformat)
1765		_, err = stmtSaveXonker.Exec(xid, handle, "handle", when)
1766		if err != nil {
1767			elog.Printf("error saving handle: %s", err)
1768		}
1769	}
1770}
1771
1772func updateMe(username string) {
1773	var user *WhatAbout
1774	somenamedusers.Get(username, &user)
1775	dt := time.Now().UTC().Format(time.RFC3339)
1776	j := junk.New()
1777	j["@context"] = itiswhatitis
1778	j["id"] = fmt.Sprintf("%s/upme/%s/%d", user.URL, user.Name, time.Now().Unix())
1779	j["actor"] = user.URL
1780	j["published"] = dt
1781	j["to"] = thewholeworld
1782	j["type"] = "Update"
1783	j["object"] = junkuser(user)
1784
1785	msg := j.ToBytes()
1786
1787	rcpts := make(map[string]bool)
1788	for _, f := range getdubs(user.ID) {
1789		if f.XID == user.URL {
1790			continue
1791		}
1792		var box *Box
1793		boxofboxes.Get(f.XID, &box)
1794		if box != nil && box.Shared != "" {
1795			rcpts["%"+box.Shared] = true
1796		} else {
1797			rcpts[f.XID] = true
1798		}
1799	}
1800	for a := range rcpts {
1801		go deliverate(0, user.ID, a, msg, false)
1802	}
1803}
1804
1805func followme(user *WhatAbout, who string, name string, j junk.Junk) {
1806	folxid, _ := j.GetString("id")
1807
1808	ilog.Printf("updating honker follow: %s %s", who, folxid)
1809
1810	var x string
1811	db := opendatabase()
1812	row := db.QueryRow("select xid from honkers where name = ? and xid = ? and userid = ? and flavor in ('dub', 'undub')", name, who, user.ID)
1813	err := row.Scan(&x)
1814	if err != sql.ErrNoRows {
1815		ilog.Printf("duplicate follow request: %s", who)
1816		_, err = stmtUpdateFlavor.Exec("dub", folxid, user.ID, name, who, "undub")
1817		if err != nil {
1818			elog.Printf("error updating honker: %s", err)
1819		}
1820	} else {
1821		stmtSaveDub.Exec(user.ID, name, who, "dub", folxid)
1822	}
1823	go rubadubdub(user, j)
1824}
1825
1826func unfollowme(user *WhatAbout, who string, name string, j junk.Junk) {
1827	var folxid string
1828	if who == "" {
1829		folxid, _ = j.GetString("object")
1830
1831		db := opendatabase()
1832		row := db.QueryRow("select xid, name from honkers where userid = ? and folxid = ? and flavor in ('dub', 'undub')", user.ID, folxid)
1833		err := row.Scan(&who, &name)
1834		if err != nil {
1835			if err != sql.ErrNoRows {
1836				elog.Printf("error scanning honker: %s", err)
1837			}
1838			return
1839		}
1840	}
1841
1842	ilog.Printf("updating honker undo: %s %s", who, folxid)
1843	_, err := stmtUpdateFlavor.Exec("undub", folxid, user.ID, name, who, "dub")
1844	if err != nil {
1845		elog.Printf("error updating honker: %s", err)
1846		return
1847	}
1848}
1849
1850func followyou(user *WhatAbout, honkerid int64) {
1851	var url, owner string
1852	db := opendatabase()
1853	row := db.QueryRow("select xid, owner from honkers where honkerid = ? and userid = ? and flavor in ('unsub', 'peep', 'presub', 'sub')",
1854		honkerid, user.ID)
1855	err := row.Scan(&url, &owner)
1856	if err != nil {
1857		elog.Printf("can't get honker xid: %s", err)
1858		return
1859	}
1860	folxid := xfiltrate()
1861	ilog.Printf("subscribing to %s", url)
1862	_, err = db.Exec("update honkers set flavor = ?, folxid = ? where honkerid = ?", "presub", folxid, honkerid)
1863	if err != nil {
1864		elog.Printf("error updating honker: %s", err)
1865		return
1866	}
1867	go subsub(user, url, owner, folxid)
1868
1869}
1870func unfollowyou(user *WhatAbout, honkerid int64) {
1871	db := opendatabase()
1872	row := db.QueryRow("select xid, owner, folxid from honkers where honkerid = ? and userid = ? and flavor in ('sub')",
1873		honkerid, user.ID)
1874	var url, owner, folxid string
1875	err := row.Scan(&url, &owner, &folxid)
1876	if err != nil {
1877		elog.Printf("can't get honker xid: %s", err)
1878		return
1879	}
1880	ilog.Printf("unsubscribing from %s", url)
1881	_, err = db.Exec("update honkers set flavor = ? where honkerid = ?", "unsub", honkerid)
1882	if err != nil {
1883		elog.Printf("error updating honker: %s", err)
1884		return
1885	}
1886	go itakeitallback(user, url, owner, folxid)
1887}
1888
1889func followyou2(user *WhatAbout, j junk.Junk) {
1890	who, _ := j.GetString("actor")
1891
1892	ilog.Printf("updating honker accept: %s", who)
1893	db := opendatabase()
1894	row := db.QueryRow("select name, folxid from honkers where userid = ? and xid = ? and flavor in ('presub')",
1895		user.ID, who)
1896	var name, folxid string
1897	err := row.Scan(&name, &folxid)
1898	if err != nil {
1899		elog.Printf("can't get honker name: %s", err)
1900		return
1901	}
1902	_, err = stmtUpdateFlavor.Exec("sub", folxid, user.ID, name, who, "presub")
1903	if err != nil {
1904		elog.Printf("error updating honker: %s", err)
1905		return
1906	}
1907}
1908
1909func nofollowyou2(user *WhatAbout, j junk.Junk) {
1910	who, _ := j.GetString("actor")
1911
1912	ilog.Printf("updating honker reject: %s", who)
1913	db := opendatabase()
1914	row := db.QueryRow("select name, folxid from honkers where userid = ? and xid = ? and flavor in ('presub', 'sub')",
1915		user.ID, who)
1916	var name, folxid string
1917	err := row.Scan(&name, &folxid)
1918	if err != nil {
1919		elog.Printf("can't get honker name: %s", err)
1920		return
1921	}
1922	_, err = stmtUpdateFlavor.Exec("unsub", folxid, user.ID, name, who, "presub")
1923	_, err = stmtUpdateFlavor.Exec("unsub", folxid, user.ID, name, who, "sub")
1924	if err != nil {
1925		elog.Printf("error updating honker: %s", err)
1926		return
1927	}
1928}