all repos — honk @ f8dfc0b4658be50b27e20831673b876823ed6c29

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