all repos — honk @ 66c6ced8bf552408c833216614873a616b2720dc

my fork of honk

activity.go (view raw)

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