all repos — honk @ 1d3a86682b0bd742c02da7dbbdb6c39cac3a1ecb

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