all repos — honk @ dc922e7d5b66206ef04e2e2f36a13d178d78bf10

my fork of honk

m
Ted Unangst tedu@tedunangst.com
Tue, 10 Dec 2019 18:12:29 -0500
commit

dc922e7d5b66206ef04e2e2f36a13d178d78bf10

parent

db582287736dac7593852ab064267de1d2d37b55

A .hgignore

@@ -0,0 +1,4 @@

+.*\.db +memes +emus +honk
M READMEREADME

@@ -63,3 +63,7 @@ It is considered rude to make noise in a place of business.

The honk may be made on public property only when the person doing the honk has the permission of the owner of that property. + +-- disclaimer + +Do not use honk to contact emergency services.
M activity.goactivity.go

@@ -19,6 +19,7 @@ import (

"bytes" "crypto/rsa" "database/sql" + "errors" "fmt" "html" "io"

@@ -141,6 +142,22 @@ j := ji.(junk.Junk)

return j, nil } +func fetchsome(url string) ([]byte, error) { + resp, err := http.Get(url) + if err != nil { + log.Printf("error fetching %s: %s", url, err) + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + return nil, errors.New("not 200") + } + var buf bytes.Buffer + limiter := io.LimitReader(resp.Body, 10*1024*1024) + io.Copy(&buf, limiter) + return buf.Bytes(), nil +} + func savedonk(url string, name, desc, media string, localize bool) *Donk { if url == "" { return nil

@@ -154,22 +171,16 @@ log.Printf("saving donk: %s", url)

xid := xfiltrate() data := []byte{} if localize { - resp, err := http.Get(url) - if err != nil { - log.Printf("error fetching %s: %s", url, err) - localize = false - goto saveit + fn := func() (interface{}, error) { + return fetchsome(url) } - defer resp.Body.Close() - if resp.StatusCode != 200 { + ii, err := flightdeck.Call(url, fn) + if err != nil { localize = false goto saveit } - var buf bytes.Buffer - limiter := io.LimitReader(resp.Body, 10*1024*1024) - io.Copy(&buf, limiter) + data = ii.([]byte) - data = buf.Bytes() if len(data) == 10*1024*1024 { log.Printf("truncation likely") }

@@ -188,6 +199,12 @@ if format == "jpeg" {

format = "jpg" } xid = xid + "." + format + } else if media == "application/pdf" { + if len(data) > 1000000 { + log.Printf("not saving large pdf") + localize = false + data = []byte{} + } } else if len(data) > 100000 { log.Printf("not saving large attachment") localize = false

@@ -224,11 +241,20 @@ return false

} return needxonkid(user, x.XID) } +func needbonkid(user *WhatAbout, xid string) bool { + return needxonkidX(user, xid, true) +} func needxonkid(user *WhatAbout, xid string) bool { + return needxonkidX(user, xid, false) +} +func needxonkidX(user *WhatAbout, xid string, isannounce bool) bool { + if !strings.HasPrefix(xid, "https://") { + return false + } if strings.HasPrefix(xid, user.URL+"/") { return false } - if rejectorigin(user.ID, xid) { + if rejectorigin(user.ID, xid, isannounce) { return false } if iszonked(user.ID, xid) {

@@ -277,7 +303,8 @@ row := stmtGetXonker.QueryRow(ident, "boxes")

err := row.Scan(&info) if err != nil { log.Printf("need to get boxes for %s", ident) - j, err := GetJunk(ident) + var j junk.Junk + j, err = GetJunk(ident) if err != nil { log.Printf("error getting boxes: %s", err) return nil, false

@@ -483,7 +510,7 @@ xid, _ = obj.GetString("id")

} else { xid, _ = item.GetString("object") } - if !needxonkid(user, xid) { + if !needbonkid(user, xid) { return nil } log.Printf("getting bonk: %s", xid)

@@ -617,7 +644,7 @@ }

xonk.Audience = append(xonk.Audience, xonk.Honker) xonk.Audience = oneofakind(xonk.Audience) - var mentions []string + var mentions []Mention if obj != nil { ot, _ := obj.GetString("type") url, _ = obj.GetString("url")

@@ -697,7 +724,8 @@ log.Printf("excessive attachment: %s", at)

} else if at == "Document" || at == "Image" { mt = strings.ToLower(mt) log.Printf("attachment: %s %s", mt, u) - if mt == "text/plain" || strings.HasPrefix(mt, "image") { + if mt == "text/plain" || mt == "application/pdf" || + strings.HasPrefix(mt, "image") { localize = true } } else {

@@ -754,7 +782,9 @@ p.Url, _ = tag.GetString("url")

xonk.Place = p } if tt == "Mention" { - m, _ := tag.GetString("href") + var m Mention + m.Who, _ = tag.GetString("name") + m.Where, _ = tag.GetString("href") mentions = append(mentions, m) } }

@@ -833,8 +863,9 @@ xonk.Noise = content

xonk.Precis = precis xonk.Format = "html" xonk.Convoy = convoy + xonk.Mentions = mentions for _, m := range mentions { - if m == user.URL { + if m.Where == user.URL { xonk.Whofore = 1 } }

@@ -847,14 +878,8 @@ if prev == nil {

log.Printf("didn't find old version for update: %s", xonk.XID) isUpdate = false } else { - prev.Noise = xonk.Noise - prev.Precis = xonk.Precis - prev.Date = xonk.Date - prev.Donks = xonk.Donks - prev.Onts = xonk.Onts - prev.Place = xonk.Place - prev.Whofore = xonk.Whofore - updatehonk(prev) + xonk.ID = prev.ID + updatehonk(&xonk) } } if !isUpdate && needxonk(user, &xonk) {

@@ -883,7 +908,7 @@ if convoy == "" {

convoy = currenttid } if convoy == "" { - convoy = "missing-" + xfiltrate() + convoy = "data:,missing-" + xfiltrate() currenttid = convoy } xonk.Convoy = convoy

@@ -1008,10 +1033,11 @@ if !h.Public {

jo["directMessage"] = true } mentions := bunchofgrapes(h.Noise) - translate(h, true) + translate(h) + redoimages(h) jo["summary"] = html.EscapeString(h.Precis) jo["content"] = h.Noise - if strings.HasPrefix(h.Precis, "DZ:") { + if h.Precis != "" { jo["sensitive"] = true }

@@ -1031,8 +1057,8 @@ var tags []junk.Junk

for _, m := range mentions { t := junk.New() t["type"] = "Mention" - t["name"] = m.who - t["href"] = m.where + t["name"] = m.Who + t["href"] = m.Where tags = append(tags, t) } for _, o := range h.Onts {

@@ -1167,24 +1193,34 @@ ok := oldjonks.Get(xid, &j)

return j, ok } -func honkworldwide(user *WhatAbout, honk *Honk) { - jonk, _ := jonkjonk(user, honk) - jonk["@context"] = itiswhatitis - msg := jonk.ToBytes() - +func boxuprcpts(user *WhatAbout, addresses []string, useshared bool) map[string]bool { rcpts := make(map[string]bool) - for _, a := range honk.Audience { - if a == thewholeworld || a == user.URL || strings.HasSuffix(a, "/followers") { + for _, a := range addresses { + if a == "" || a == thewholeworld || a == user.URL || strings.HasSuffix(a, "/followers") { + continue + } + if a[0] == '%' { + rcpts[a] = true continue } var box *Box ok := boxofboxes.Get(a, &box) - if ok && honk.Public && box.Shared != "" { + if ok && useshared && box.Shared != "" { rcpts["%"+box.Shared] = true } else { rcpts[a] = true } } + return rcpts +} + +func honkworldwide(user *WhatAbout, honk *Honk) { + jonk, _ := jonkjonk(user, honk) + jonk["@context"] = itiswhatitis + msg := jonk.ToBytes() + + rcpts := boxuprcpts(user, honk.Audience, honk.Public) + if honk.Public { for _, h := range getdubs(user.ID) { if h.XID == user.URL {

@@ -1197,6 +1233,9 @@ rcpts["%"+box.Shared] = true

} else { rcpts[h.XID] = true } + } + for _, f := range getbacktracks(honk.XID) { + rcpts[f] = true } } for a := range rcpts {

@@ -1317,7 +1356,8 @@ href, _ := l.GetString("href")

rel, _ := l.GetString("rel") t, _ := l.GetString("type") if rel == "self" && friendorfoe(t) { - _, err := stmtSaveXonker.Exec(name, href, "fishname") + when := time.Now().UTC().Format(dbtimeformat) + _, err := stmtSaveXonker.Exec(name, href, "fishname", when) if err != nil { log.Printf("error saving fishname: %s", err) }

@@ -1430,7 +1470,8 @@ if err != nil {

log.Printf("error decoding %s pubkey: %s", keyname, err) return } - _, err = stmtSaveXonker.Exec(keyname, data, "pubkey") + when := time.Now().UTC().Format(dbtimeformat) + _, err = stmtSaveXonker.Exec(keyname, data, "pubkey", when) if err != nil { log.Printf("error saving key: %s", err) }

@@ -1455,8 +1496,9 @@ inbox, _ := obj.GetString("inbox")

outbox, _ := obj.GetString("outbox") sbox, _ := obj.GetString("endpoints", "sharedInbox") if inbox != "" { + when := time.Now().UTC().Format(dbtimeformat) m := strings.Join([]string{inbox, outbox, sbox}, " ") - _, err = stmtSaveXonker.Exec(ident, m, "boxes") + _, err = stmtSaveXonker.Exec(ident, m, "boxes", when) if err != nil { log.Printf("error saving boxes: %s", err) }

@@ -1479,7 +1521,8 @@ return

} handle, _ = obj.GetString("preferredUsername") if handle != "" { - _, err = stmtSaveXonker.Exec(xid, handle, "handle") + when := time.Now().UTC().Format(dbtimeformat) + _, err = stmtSaveXonker.Exec(xid, handle, "handle", when) if err != nil { log.Printf("error saving handle: %s", err) }
M admin.goadmin.go

@@ -36,6 +36,9 @@ esc := "\x1b"

smcup := esc + "[?1049h" rmcup := esc + "[?1049l" + var avatarColors string + getconfig("avatarcolors", &avatarColors) + messages := []*struct { name string label string

@@ -55,6 +58,11 @@ {

name: "loginmsg", label: "login", text: string(loginMsg), + }, + { + name: "avatarcolors", + label: "avatar colors (4 RGBA hex numbers)", + text: string(avatarColors), }, } cursel := 0

@@ -239,7 +247,7 @@ }

stdout.Flush() } editing = false - updateconfig(m.name, m.text) + setconfig(m.name, m.text) hidecursor() drawscreen() }
M avatar.goavatar.go

@@ -16,13 +16,52 @@

package main import ( + "bufio" "bytes" "crypto/sha512" "image" "image/png" + "log" + "strconv" + "strings" ) -func avatar(name string) []byte { +var avatarcolors = [4][4]byte{ + {16, 0, 48, 255}, + {48, 0, 96, 255}, + {72, 0, 144, 255}, + {96, 0, 192, 255}, +} + +func loadAvatarColors() { + var colors string + getconfig("avatarcolors", &colors) + if colors == "" { + return + } + r := bufio.NewReader(strings.NewReader(colors)) + for i := 0; i < 4; i++ { + l, _ := r.ReadString(' ') + for l == " " { + l, _ = r.ReadString(' ') + } + l = strings.Trim(l, "# \n") + if len(l) == 6 { + l = l + "ff" + } + c, err := strconv.ParseUint(l, 16, 32) + if err != nil { + log.Printf("error reading avatar color %d: %s", i, err) + continue + } + avatarcolors[i][0] = byte(c >> 24 & 0xff) + avatarcolors[i][1] = byte(c >> 16 & 0xff) + avatarcolors[i][2] = byte(c >> 8 & 0xff) + avatarcolors[i][3] = byte(c >> 0 & 0xff) + } +} + +func genAvatar(name string) []byte { h := sha512.New() h.Write([]byte(name)) s := h.Sum(nil)

@@ -33,25 +72,25 @@ p := i*img.Stride + j*4

xx := i/16*16 + j/16 x := s[xx] if x < 64 { - img.Pix[p+0] = 16 - img.Pix[p+1] = 0 - img.Pix[p+2] = 48 - img.Pix[p+3] = 255 + img.Pix[p+0] = avatarcolors[0][0] + img.Pix[p+1] = avatarcolors[0][1] + img.Pix[p+2] = avatarcolors[0][2] + img.Pix[p+3] = avatarcolors[0][3] } else if x < 128 { - img.Pix[p+0] = 48 - img.Pix[p+1] = 0 - img.Pix[p+2] = 96 - img.Pix[p+3] = 255 + img.Pix[p+0] = avatarcolors[1][0] + img.Pix[p+1] = avatarcolors[1][1] + img.Pix[p+2] = avatarcolors[1][2] + img.Pix[p+3] = avatarcolors[1][3] } else if x < 192 { - img.Pix[p+0] = 72 - img.Pix[p+1] = 0 - img.Pix[p+2] = 144 - img.Pix[p+3] = 255 + img.Pix[p+0] = avatarcolors[2][0] + img.Pix[p+1] = avatarcolors[2][1] + img.Pix[p+2] = avatarcolors[2][2] + img.Pix[p+3] = avatarcolors[2][3] } else { - img.Pix[p+0] = 96 - img.Pix[p+1] = 0 - img.Pix[p+2] = 192 - img.Pix[p+3] = 255 + img.Pix[p+0] = avatarcolors[3][0] + img.Pix[p+1] = avatarcolors[3][1] + img.Pix[p+2] = avatarcolors[3][2] + img.Pix[p+3] = avatarcolors[3][3] } } }
M backend.gobackend.go

@@ -23,6 +23,7 @@ "net/rpc"

"os" "os/exec" + "humungus.tedunangst.com/r/webs/gate" "humungus.tedunangst.com/r/webs/image" )

@@ -38,7 +39,11 @@ type ShrinkerResult struct {

Image *image.Image } +var shrinkgate = gate.NewLimiter(4) + func (s *Shrinker) Shrink(args *ShrinkerArgs, res *ShrinkerResult) error { + shrinkgate.Start() + defer shrinkgate.Finish() img, err := image.Vacuum(bytes.NewReader(args.Buf), args.Params) if err != nil { return err

@@ -68,6 +73,8 @@ }

return res.Image, nil } +var backendhooks []func() + func backendServer() { log.Printf("backend server running") shrinker := new(Shrinker)

@@ -87,7 +94,7 @@ lis, err := net.Listen("unix", sockname)

if err != nil { log.Panicf("unable to register shrinker: %s", err) } - for _, h := range preservehooks { + for _, h := range backendhooks { h() } srv.Accept(lis)
M database.godatabase.go

@@ -101,13 +101,16 @@ defer rows.Close()

var honkers []*Honker for rows.Next() { h := new(Honker) - var combos string - err = rows.Scan(&h.ID, &h.UserID, &h.Name, &h.XID, &h.Flavor, &combos) - h.Combos = strings.Split(strings.TrimSpace(combos), " ") + var combos, meta string + err = rows.Scan(&h.ID, &h.UserID, &h.Name, &h.XID, &h.Flavor, &combos, &meta) + if err == nil { + err = unjsonify(meta, &h.Meta) + } if err != nil { log.Printf("error scanning honker: %s", err) - return nil + continue } + h.Combos = strings.Split(strings.TrimSpace(combos), " ") honkers = append(honkers, h) } return honkers

@@ -249,8 +252,10 @@ honker := ""

withhonker := 0 site := "" withsite := 0 + withnotq := 0 terms := strings.Split(q, " ") q = "%" + notq := "%" for _, t := range terms { if strings.HasPrefix(t, "site:") { site = t[5:]

@@ -267,13 +272,27 @@ }

withhonker = 1 continue } + if t[0] == '-' { + if t == "-" { + continue + } + if len(notq) != 1 { + notq += " " + } + notq += t[1:] + continue + } if len(q) != 1 { q += " " } q += t } q += "%" - rows, err := stmtHonksBySearch.Query(wanted, userid, withsite, site, withhonker, honker, honker, q, userid) + notq += "%" + if notq != "%%" { + withnotq = 1 + } + rows, err := stmtHonksBySearch.Query(wanted, userid, withsite, site, withhonker, honker, honker, q, withnotq, notq, userid) honks := getsomehonks(rows, err) return honks }

@@ -413,6 +432,12 @@ log.Printf("error parsing time: %s", err)

continue } h.Time = t + case "mentions": + err = unjsonify(j, &h.Mentions) + if err != nil { + log.Printf("error parsing mentions: %s", err) + continue + } case "oldrev": default: log.Printf("unknown meta genus: %s", genus)

@@ -491,7 +516,7 @@ log.Printf("can't begin tx: %s", err)

return err } - err = deleteextras(tx, h.ID) + err = deleteextras(tx, h.ID, false) if err == nil { _, err = tx.Stmt(stmtUpdateHonk).Exec(h.Precis, h.Noise, h.Format, h.Whofore, dt, h.ID) }

@@ -527,10 +552,7 @@ log.Printf("can't begin tx: %s", err)

return err } - err = deleteextras(tx, honkid) - if err == nil { - _, err = tx.Stmt(stmtDeleteMeta).Exec(honkid, "nonsense") - } + err = deleteextras(tx, honkid, true) if err == nil { _, err = tx.Stmt(stmtDeleteHonk).Exec(honkid) }

@@ -580,10 +602,20 @@ log.Printf("error saving time: %s", err)

return err } } + if m := h.Mentions; len(m) > 0 { + j, err := jsonify(m) + if err == nil { + _, err = tx.Stmt(stmtSaveMeta).Exec(h.ID, "mentions", j) + } + if err != nil { + log.Printf("error saving mentions: %s", err) + return err + } + } return nil } -func deleteextras(tx *sql.Tx, honkid int64) error { +func deleteextras(tx *sql.Tx, honkid int64, everything bool) error { _, err := tx.Stmt(stmtDeleteDonks).Exec(honkid) if err != nil { return err

@@ -592,7 +624,11 @@ _, err = tx.Stmt(stmtDeleteOnts).Exec(honkid)

if err != nil { return err } - _, err = tx.Stmt(stmtDeleteMeta).Exec(honkid, "oldrev") + if everything { + _, err = tx.Stmt(stmtDeleteAllMeta).Exec(honkid) + } else { + _, err = tx.Stmt(stmtDeleteSomeMeta).Exec(honkid) + } if err != nil { return err }

@@ -691,8 +727,10 @@ var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover, stmtOneHonker *sql.Stmt

var stmtUntagged, stmtDeleteHonk, stmtDeleteDonks, stmtDeleteOnts, stmtSaveZonker *sql.Stmt var stmtGetZonkers, stmtRecentHonkers, stmtGetXonker, stmtSaveXonker, stmtDeleteXonker *sql.Stmt var stmtAllOnts, stmtSaveOnt, stmtUpdateFlags, stmtClearFlags *sql.Stmt -var stmtHonksForUserFirstClass, stmtSaveMeta, stmtDeleteMeta, stmtUpdateHonk *sql.Stmt +var stmtHonksForUserFirstClass *sql.Stmt +var stmtSaveMeta, stmtDeleteAllMeta, stmtDeleteSomeMeta, stmtUpdateHonk *sql.Stmt var stmtHonksISaved, stmtGetFilters, stmtSaveFilter, stmtDeleteFilter *sql.Stmt +var stmtGetTracks *sql.Stmt func preparetodie(db *sql.DB, s string) *sql.Stmt { stmt, err := db.Prepare(s)

@@ -703,10 +741,10 @@ return stmt

} func prepareStatements(db *sql.DB) { - stmtHonkers = preparetodie(db, "select honkerid, userid, name, xid, flavor, combos from honkers where userid = ? and (flavor = 'presub' or flavor = 'sub' or flavor = 'peep' or flavor = 'unsub') order by name") - stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos, owner) values (?, ?, ?, ?, ?, ?)") + stmtHonkers = preparetodie(db, "select honkerid, userid, name, xid, flavor, combos, meta from honkers where userid = ? and (flavor = 'presub' or flavor = 'sub' or flavor = 'peep' or flavor = 'unsub') order by name") + stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos, owner, meta) values (?, ?, ?, ?, ?, ?, ?)") stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ? where userid = ? and xid = ? and name = ? and flavor = ?") - stmtUpdateHonker = preparetodie(db, "update honkers set name = ?, combos = ? where honkerid = ? and userid = ?") + stmtUpdateHonker = preparetodie(db, "update honkers set name = ?, combos = ?, meta = ? where honkerid = ? and userid = ?") stmtOneHonker = preparetodie(db, "select xid from honkers where name = ? and userid = ?") stmtDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and flavor = 'dub'") stmtNamedDubbers = preparetodie(db, "select honkerid, userid, name, xid, flavor from honkers where userid = ? and name = ? and flavor = 'dub'")

@@ -729,12 +767,13 @@ stmtHonksISaved = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and flags & 4 order by honks.honkid desc")

stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on (honkers.xid = honks.honker or honkers.xid = honks.oonker) where honks.honkid > ? and honks.userid = ? and honkers.name = ?"+butnotthose+limit) stmtHonksByXonker = preparetodie(db, selecthonks+" where honks.honkid > ? and honks.userid = ? and (honker = ? or oonker = ?)"+butnotthose+limit) stmtHonksByCombo = preparetodie(db, selecthonks+" where honks.honkid > ? and honks.userid = ? and honks.honker in (select xid from honkers where honkers.userid = ? and honkers.combos like ?) "+butnotthose+" union "+selecthonks+"join onts on honks.honkid = onts.honkid where honks.honkid > ? and honks.userid = ? and onts.ontology in (select xid from honkers where combos like ?)"+butnotthose+limit) - stmtHonksBySearch = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and (? = 0 or xid like ?) and (? = 0 or honks.honker = ? or honks.oonker = ?) and noise like ?"+butnotthose+limit) + stmtHonksBySearch = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and (? = 0 or xid like ?) and (? = 0 or honks.honker = ? or honks.oonker = ?) and noise like ? and (? = 0 or noise not like ?)"+butnotthose+limit) stmtHonksByConvoy = preparetodie(db, selecthonks+"where honks.honkid > ? and (honks.userid = ? or (? = -1 and whofore = 2)) and convoy = ?"+limit) stmtHonksByOntology = preparetodie(db, selecthonks+"join onts on honks.honkid = onts.honkid where honks.honkid > ? and onts.ontology = ? and (honks.userid = ? or (? = -1 and honks.whofore = 2))"+limit) stmtSaveMeta = preparetodie(db, "insert into honkmeta (honkid, genus, json) values (?, ?, ?)") - stmtDeleteMeta = preparetodie(db, "delete from honkmeta where honkid = ? and genus <> ?") + stmtDeleteAllMeta = preparetodie(db, "delete from honkmeta where honkid = ?") + stmtDeleteSomeMeta = preparetodie(db, "delete from honkmeta where honkid = ? and genus not in ('oldrev')") stmtSaveHonk = preparetodie(db, "insert into honks (userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker, flags) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") stmtDeleteHonk = preparetodie(db, "delete from honks where honkid = ?") stmtUpdateHonk = preparetodie(db, "update honks set precis = ?, noise = ?, format = ?, whofore = ?, dt = ? where honkid = ?")

@@ -760,8 +799,8 @@ stmtFindZonk = preparetodie(db, "select zonkerid from zonkers where userid = ? and name = ? and wherefore = 'zonk'")

stmtGetZonkers = preparetodie(db, "select zonkerid, name, wherefore from zonkers where userid = ? and wherefore <> 'zonk'") stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)") stmtGetXonker = preparetodie(db, "select info from xonkers where name = ? and flavor = ?") - stmtSaveXonker = preparetodie(db, "insert into xonkers (name, info, flavor) values (?, ?, ?)") - stmtDeleteXonker = preparetodie(db, "delete from xonkers where name = ? and flavor = ?") + stmtSaveXonker = preparetodie(db, "insert into xonkers (name, info, flavor, dt) values (?, ?, ?, ?)") + stmtDeleteXonker = preparetodie(db, "delete from xonkers where name = ? and flavor = ? and dt < ?") stmtRecentHonkers = preparetodie(db, "select distinct(honker) from honks where userid = ? and honker not in (select xid from honkers where userid = ? and flavor = 'sub') order by honkid desc limit 100") stmtUpdateFlags = preparetodie(db, "update honks set flags = flags | ? where honkid = ?") stmtClearFlags = preparetodie(db, "update honks set flags = flags & ~ ? where honkid = ?")

@@ -769,4 +808,5 @@ stmtAllOnts = preparetodie(db, "select ontology, count(ontology) from onts join honks on onts.honkid = honks.honkid where (honks.userid = ? or honks.whofore = 2) group by ontology")

stmtGetFilters = preparetodie(db, "select hfcsid, json from hfcs where userid = ?") stmtSaveFilter = preparetodie(db, "insert into hfcs (userid, json) values (?, ?)") stmtDeleteFilter = preparetodie(db, "delete from hfcs where userid = ? and hfcsid = ?") + stmtGetTracks = preparetodie(db, "select fetches from tracks where xid = ?") }
M docs/changelog.txtdocs/changelog.txt

@@ -1,6 +1,38 @@

changelog --- next +=== next + ++ Configurable avatar colors. + ++ Optional pleroma color scheme for the home sick... + ++ Rebalance colors slightly. Looks a little fresher now? + ++ Add unplug command for servers that have dropped off the net. + ++ Add notes field to honkers to document their downfall. + ++ Add notes field to filters for record keeping. + ++ Negated search -terms. + ++ A raw sendactivity API action for the bold. + ++ More flexible meme names. + +=== 0.8.5 Turnkey Blaster + ++ Codenames in changelog. + ++ Fix some bugs that may have interfered with federation. + ++ Add some re: re: re: to replies. + ++ Set an avatar. If you must. + ++ Try a little harder to recover from httpsig failures. + ++ Add cite tag for block quote attributions. + deluser command.

@@ -10,17 +42,17 @@ + Disable overscroll (pull down) refresh.

+ Can never seem to version the changelog correctly. --- 0.8.4 +=== 0.8.4 + Fix bug preventing import of keys + Option to switch map links to Apple. --- 0.8.3 +=== 0.8.3 - mistag. --- 0.8.2 +=== 0.8.2 Game Warden ++ Import command to preserve those embarssassing old posts from Twitter.

@@ -36,7 +68,7 @@ + Rewrite admin console to avoid large dependencies.

+ "Bug" fixes. --- 0.8.1 +=== 0.8.1 ++ Make it easier to upgrade by decoupling data dir from ".".

@@ -48,7 +80,7 @@ + Amend changelog for 0.8.0 to include omitted elements:

Syntax highlighting for code blocks. Something resembling an actual manual. --- 0.8.0 +=== 0.8.0 Ordinary Octology +++ Add Honk Filtering and Censorship System (HFCS).

@@ -103,7 +135,7 @@

- Sometimes the cached state of the @me feed becomes unsynced. Acked status may display incorrectly. --- 0.7.7 +=== 0.7.7 More 7 Than Ever + Add another retry to workaround pixelfed's general unreliability.

@@ -115,11 +147,11 @@ + Keep reply to setting during preview.

+ Increase max thread retrieval depth to 10. --- 0.7.6 +=== 0.7.6 + Fix a bug where upgrades would not complete in one step. --- 0.7.5 +=== 0.7.5 + Fix a bug (introdcued 0.7.4) preventing new user creation from working.

@@ -131,7 +163,7 @@ + Add server name to user agent.

+ What may be considered UI improvements. --- 0.7.4 +=== 0.7.4 + Ever more bug fixes.

@@ -147,19 +179,19 @@ + Support for some user selectable styling. Currently, skinny column mode.

+ webp image transcoding. --- 0.7.3 +=== 0.7.3 + Better fedicompat so bonks are visible to pleroma followers. --- 0.7.2 +=== 0.7.2 + Add the funzone. Minor other UI tweaks. --- 0.7.1 +=== 0.7.1 + Fix bug preventing unfollow from working. --- 0.7.0 +=== 0.7.0 Father Mother Maiden Crone Honker Bonker Zonker +++ Auto fetching and inlining of hoots.

@@ -189,6 +221,12 @@ + Not all summaries need labels.

+ Add max-width for video tag. --- 0.6.0 and prior +=== 0.6.0 Sixy Delights + +Most records from this time of primitive development have been lost. -All records from this time of primitive development have been lost. +=== 0.5.0 Halfway to Heaven + +=== 0.4.0 Fore Score + +=== 0.3.0 Valorous Varaha
M docs/hfcs.1docs/hfcs.1

@@ -28,6 +28,12 @@ It is accessed via the

.Pa filters menu item. .Pp +Each filter has an optional +.Ar name +and +.Ar notes +for user defined purposes. +.Pp The following match types are possible. All nonempty criteria must match. .Bl -tag -width include-audience
M docs/honk.1docs/honk.1

@@ -43,15 +43,18 @@ .Ar url

field is required. Either of two forms are accepted, the user's handle (or webfinger) or their ActivityPub actor URL. +.Pp +.Dl @user@example.social +.Dl https://example.social/users/user +.Pp The .Ar name field is optional and will be automatically inferred. -Examples: -.Dl @user@example.social -.Dl https://example.social/users/user -.Pp +The +.Ar notes +field is reserved for user remarks. Fellow honkers may be added to one or more -.Ic combos +.Ar combos to suit one's organizational preferences. These are accessed via the .Pa combos

@@ -143,11 +146,14 @@ .It site

Substring match on the post domain name. .It honker Exact match, either AP actor or honker nickname. +.It - +Negate term. .El .Pp Example: -.Dl honker:goose big moose -This query will find honks by the goose about the big moose. +.Dl honker:goose big moose -footloose +This query will find honks by the goose about the big moose, but excluding +those about footloose. .Ss Filtering Sometimes other users of the federation can get unruly. The honk filtering and censorship system,

@@ -164,6 +170,12 @@ It also allows the import of external objects via URL, either individual

posts or actor URLs, in which case their recent outbox is imported. .Ss Account It's all about you. +An avatar may be selected from the +.Pa funzone +by adding +.Dq avatar: filename.png +to one's profile info. +If truly necessary. .Pp Some options to customize the site appearance: .Bl -tag -width skinny
M docs/honk.3docs/honk.3

@@ -109,6 +109,20 @@ If there are no results, wait this many seconds for something to appear.

.El .Pp The result will be returned as json. +.Ss sendactivity +Send anything. +No limits, no error checking. +.Bl -tag -width public +.It Fa rcpt +An actor to deliver the message to to. +May be specified more than once. +An inbox may be specified directly by prefixing with %. +.It Fa msg +The message. +It should be a valid json activity, but yolo. +.It Fa public +Set to 1 to use shared inboxes for delivery. +.El .Sh EXAMPLES Refer to the sample code in the .Pa toys
M docs/honk.8docs/honk.8

@@ -100,6 +100,8 @@ .It about

Displayed on the about page. .It login Displayed about the login form. +.It avatar colors +Four 32-bit hex colors (RGBA). .El .Pp .Ss User Admin

@@ -137,6 +139,12 @@ .Pp

The current version of the honk binary may be printed with the .Ic version command. +.Ss unplug +Sometimes servers simply disappear, resulting in many errors trying to deliver +undeliverable messages. +Running +.Ic unplug Ar hostname +will delete all subscriptions and pending deliveries. .Ss Security .Nm is not currently hardened against SSRF, server side request forgery.

@@ -147,6 +155,7 @@ Debug mode may be enabled or disabled by running

.Ic debug Ar on|off . In debug mode, secure cookies are disabled and templates are reloaded every request. +Debug mode is really more useful for development, not debugging production. .Ss Import Data may be imported and converted from other services using the .Ic import

@@ -195,6 +204,12 @@ listen address: /var/www/honk.sock

server name: honk.example.com honk-v98> ./honk -datadir ../honkdata admin honk-v98> date; ./honk -datadir ../honkdata >> log 2>&1 +.Ed +.Pp +The views directory includes a sample pleroma.css to change color scheme. +.Bd -literal -offset indent +honk-v98> mkdir ../honkdata/views +honk-v98> cp views/pleroma.css ../honkdata/views/local.css .Ed .Pp Upgrade to the next version.
M docs/mandoc.cssdocs/mandoc.css

@@ -274,15 +274,6 @@ .Li { font-style: normal;

font-weight: normal; font-family: monospace; } -/* Tooltip support. */ - -h1.Sh, h2.Ss { position: relative; } -.An, .Ar, .Cd, .Cm, .Dv, .Em, .Er, .Ev, .Fa, .Fd, .Fl, .Fn, .Ft, -.Ic, code.In, .Lb, .Lk, .Ms, .Mt, .Nd, code.Nm, .Pa, .Rs, -.St, .Sx, .Sy, .Va, .Vt, .Xr { - display: inline-block; - position: relative; } - /* Overrides to avoid excessive margins on small devices. */ @media (max-width: 37.5em) {
M fun.gofun.go

@@ -28,6 +28,7 @@ "net/url"

"os" "regexp" "strings" + "time" "golang.org/x/net/html" "humungus.tedunangst.com/r/webs/cache"

@@ -60,17 +61,18 @@ }

if !h.Public { h.Style += " limited" } - translate(h, false) - if h.Whofore == 2 || h.Whofore == 3 { - h.URL = h.XID - if h.What != "bonked" { - h.Noise = re_memes.ReplaceAllString(h.Noise, "") - h.Noise = mentionize(h.Noise) - h.Noise = ontologize(h.Noise) - } - h.Username, h.Handle = handles(h.Honker) - } else { - _, h.Handle = handles(h.Honker) + translate(h) + local := false + if (h.Whofore == 2 || h.Whofore == 3) && h.What != "bonked" { + local = true + } + if local { + h.Noise = re_memes.ReplaceAllString(h.Noise, "") + h.Noise = mentionize(h.Noise) + h.Noise = ontologize(h.Noise) + } + h.Username, h.Handle = handles(h.Honker) + if !local { short := shortname(userid, h.Honker) if short != "" { h.Username = short

@@ -80,9 +82,9 @@ if len(h.Username) > 20 {

h.Username = h.Username[:20] + ".." } } - if h.URL == "" { - h.URL = h.XID - } + } + if h.URL == "" { + h.URL = h.XID } if h.Oonker != "" { _, h.Oondle = handles(h.Oonker)

@@ -129,6 +131,13 @@ return fmt.Sprintf(`<img class="emu" title="%s" src="/d/%s">`, d.Name, d.XID)

} } } + if local { + var emu Emu + emucache.Get(e, &emu) + if emu.ID != "" { + return fmt.Sprintf(`<img class="emu" title="%s" src="%s">`, emu.Name, emu.ID) + } + } return e } h.Precis = re_emus.ReplaceAllStringFunc(h.Precis, emuxifier)

@@ -189,7 +198,7 @@ htf.BaseURL, _ = url.Parse(honk.XID)

htf.String(honk.Noise) } -func translate(honk *Honk, redoimages bool) { +func translate(honk *Honk) { if honk.Format == "html" { return }

@@ -210,31 +219,31 @@ noise = strings.TrimSpace(noise)

noise = markitzero(noise) honk.Noise = noise honk.Onts = oneofakind(ontologies(honk.Noise)) +} - if redoimages { - zap := make(map[string]bool) - { - var htf htfilter.Filter - htf.Imager = replaceimgsand(zap, true) - htf.SpanClasses = allowedclasses - p, _ := htf.String(honk.Precis) - n, _ := htf.String(honk.Noise) - honk.Precis = string(p) - honk.Noise = string(n) - } - j := 0 - for i := 0; i < len(honk.Donks); i++ { - if !zap[honk.Donks[i].XID] { - honk.Donks[j] = honk.Donks[i] - j++ - } +func redoimages(honk *Honk) { + zap := make(map[string]bool) + { + var htf htfilter.Filter + htf.Imager = replaceimgsand(zap, true) + htf.SpanClasses = allowedclasses + p, _ := htf.String(honk.Precis) + n, _ := htf.String(honk.Noise) + honk.Precis = string(p) + honk.Noise = string(n) + } + j := 0 + for i := 0; i < len(honk.Donks); i++ { + if !zap[honk.Donks[i].XID] { + honk.Donks[j] = honk.Donks[i] + j++ } - honk.Donks = honk.Donks[:j] - - honk.Noise = re_memes.ReplaceAllString(honk.Noise, "") - honk.Noise = ontologize(mentionize(honk.Noise)) - honk.Noise = strings.Replace(honk.Noise, "<a href=", "<a class=\"mention u-url\" href=", -1) } + honk.Donks = honk.Donks[:j] + + honk.Noise = re_memes.ReplaceAllString(honk.Noise, "") + honk.Noise = ontologize(mentionize(honk.Noise)) + honk.Noise = strings.Replace(honk.Noise, "<a href=", "<a class=\"mention u-url\" href=", -1) } func xcelerate(b []byte) string {

@@ -276,11 +285,6 @@ }

return m[:j] } -type Mention struct { - who string - where string -} - var re_mentions = regexp.MustCompile(`@[[:alnum:]._-]+@[[:alnum:].-]*[[:alnum:]]`) var re_urltions = regexp.MustCompile(`@https://\S+`)

@@ -306,12 +310,12 @@ var mentions []Mention

for i := range m { where := gofish(m[i]) if where != "" { - mentions = append(mentions, Mention{who: m[i], where: where}) + mentions = append(mentions, Mention{Who: m[i], Where: where}) } } m = re_urltions.FindAllString(s, -1) for i := range m { - mentions = append(mentions, Mention{who: m[i][1:], where: m[i][1:]}) + mentions = append(mentions, Mention{Who: m[i][1:], Where: m[i][1:]}) } return mentions }

@@ -323,23 +327,33 @@ }

var re_emus = regexp.MustCompile(`:[[:alnum:]_-]+:`) +var emucache = cache.New(cache.Options{Filler: func(ename string) (Emu, bool) { + fname := ename[1 : len(ename)-1] + _, err := os.Stat(dataDir + "/emus/" + fname + ".png") + if err != nil { + return Emu{Name: ename, ID: ""}, true + } + url := fmt.Sprintf("https://%s/emu/%s.png", serverName, fname) + return Emu{ID: url, Name: ename}, true +}, Duration: 10 * time.Second}) + func herdofemus(noise string) []Emu { m := re_emus.FindAllString(noise, -1) m = oneofakind(m) var emus []Emu for _, e := range m { - fname := e[1 : len(e)-1] - _, err := os.Stat("emus/" + fname + ".png") - if err != nil { + var emu Emu + emucache.Get(e, &emu) + if emu.ID == "" { continue } - url := fmt.Sprintf("https://%s/emu/%s.png", serverName, fname) - emus = append(emus, Emu{ID: url, Name: e}) + emus = append(emus, emu) } return emus } -var re_memes = regexp.MustCompile("meme: ?([[:alnum:]_.-]+)") +var re_memes = regexp.MustCompile("meme: ?([^\n]+)") +var re_avatar = regexp.MustCompile("avatar: ?([^\n]+)") func memetize(honk *Honk) { repl := func(x string) string {

@@ -377,7 +391,7 @@ }

honk.Noise = re_memes.ReplaceAllStringFunc(honk.Noise, repl) } -var re_quickmention = regexp.MustCompile("(^|[ \n])@[[:alnum:]]+([ \n]|$)") +var re_quickmention = regexp.MustCompile("(^|[ \n])@[[:alnum:]]+([ \n.]|$)") func quickrename(s string, userid int64) string { nonstop := true

@@ -392,7 +406,7 @@ }

prefix += "@" m = m[1:] tail := "" - if m[len(m)-1] == ' ' || m[len(m)-1] == '\n' { + if last := m[len(m)-1]; last == ' ' || last == '\n' || last == '.' { tail = m[len(m)-1:] m = m[:len(m)-1] }

@@ -611,6 +625,12 @@ func zaggy(keyname string) *rsa.PublicKey {

var key *rsa.PublicKey zaggies.Get(keyname, &key) return key +} + +func savingthrow(keyname string) { + when := time.Now().UTC().Add(-30 * time.Minute).Format(dbtimeformat) + stmtDeleteXonker.Exec(keyname, "pubkey", when) + zaggies.Clear(keyname) } func keymatch(keyname string, actor string) string {
M hfcs.gohfcs.go

@@ -44,6 +44,7 @@ Rewrite string `json:",omitempty"`

re_rewrite *regexp.Regexp Replace string `json:",omitempty"` Expiration time.Time + Notes string } type filtType uint

@@ -168,14 +169,23 @@ }

return nil } -func rejectorigin(userid int64, origin string) bool { +func rejectorigin(userid int64, origin string, isannounce bool) bool { if o := originate(origin); o != "" { origin = o } filts := getfilters(userid, filtReject) for _, f := range filts { - if f.IsAnnounce || f.Text != "" { + if f.Text != "" { continue + } + if f.IsAnnounce { + if !isannounce { + continue + } + if f.AnnounceOf == origin { + log.Printf("rejecting announce: %s", origin) + return true + } } if f.Actor == origin { log.Printf("rejecting origin: %s", origin)

@@ -204,7 +214,7 @@ func stealthmode(userid int64, r *http.Request) bool {

agent := r.UserAgent() agent = originate(agent) if agent != "" { - fake := rejectorigin(userid, agent) + fake := rejectorigin(userid, agent, false) if fake { log.Printf("faking 404 for %s", agent) return true

@@ -214,21 +224,29 @@ return false

} func matchfilter(h *Honk, f *Filter) bool { + return matchfilterX(h, f) != "" +} + +func matchfilterX(h *Honk, f *Filter) string { + rv := "" match := true if match && f.Actor != "" { match = false if f.Actor == h.Honker || f.Actor == h.Oonker { match = true + rv = f.Actor } if !match && (f.Actor == originate(h.Honker) || f.Actor == originate(h.Oonker) || f.Actor == originate(h.XID)) { match = true + rv = f.Actor } if !match && f.IncludeAudience { for _, a := range h.Audience { if f.Actor == a || f.Actor == originate(a) { match = true + rv = f.Actor break } }

@@ -239,23 +257,33 @@ match = false

if (f.AnnounceOf == "" && h.Oonker != "") || f.AnnounceOf == h.Oonker || f.AnnounceOf == originate(h.Oonker) { match = true + rv += " announce" } } if match && f.Text != "" { match = false re := f.re_text - if re.MatchString(h.Noise) || re.MatchString(h.Precis) { - match = true + m := re.FindString(h.Precis) + if m == "" { + m = re.FindString(h.Noise) } - if !match { + if m == "" { for _, d := range h.Donks { - if re.MatchString(d.Desc) { - match = true + m = re.FindString(d.Desc) + if m != "" { + break } } + } + if m != "" { + match = true + rv = m } } - return match + if match { + return rv + } + return "" } func rejectxonk(xonk *Honk) bool {

@@ -282,11 +310,7 @@

func unsee(userid int64, h *Honk) { filts := getfilters(userid, filtCollapse) for _, f := range filts { - if matchfilter(h, f) { - bad := f.Text - if f.Actor != "" { - bad = f.Actor - } + if bad := matchfilterX(h, f); bad != "" { if h.Precis == "" { h.Precis = bad }
M honk.gohonk.go

@@ -88,6 +88,12 @@ Donks []*Donk

Onts []string Place *Place Time *Time + Mentions []Mention +} + +type Mention struct { + Who string + Where string } type OldRevision struct {

@@ -173,6 +179,11 @@ XID string

Handle string Flavor string Combos []string + Meta HonkerMeta +} + +type HonkerMeta struct { + Notes string } type SomeThing struct {

@@ -199,6 +210,13 @@

func ElaborateUnitTests() { } +func unplugserver(hostname string) { + db := opendatabase() + xid := fmt.Sprintf("%%https://%s/%%", hostname) + db.Exec("delete from honkers where xid like ? and flavor = 'dub'", xid) + db.Exec("delete from doovers where rcpt like ?", xid) +} + func main() { flag.StringVar(&dataDir, "datadir", dataDir, "data directory") flag.StringVar(&viewDir, "viewdir", viewDir, "view directory")

@@ -244,9 +262,9 @@ log.Fatal("need an argument: debug (on|off)")

} switch args[1] { case "on": - updateconfig("debug", 1) + setconfig("debug", 1) case "off": - updateconfig("debug", 0) + setconfig("debug", 0) default: log.Fatal("argument must be on or off") }

@@ -266,6 +284,13 @@ if len(args) > 1 {

arg = args[1] } cleanupdb(arg) + case "unplug": + if len(args) < 2 { + fmt.Printf("usage: honk unplug servername\n") + return + } + name := args[1] + unplugserver(name) case "ping": if len(args) < 3 { fmt.Printf("usage: honk ping from to\n")
M markitzero.gomarkitzero.go

@@ -28,7 +28,8 @@ var re_bolder = regexp.MustCompile(`(^|\W)\*\*((?s:.*?))\*\*($|\W)`)

var re_italicer = regexp.MustCompile(`(^|\W)\*((?s:.*?))\*($|\W)`) var re_bigcoder = regexp.MustCompile("```(.*)\n?((?s:.*?))\n?```\n?") var re_coder = regexp.MustCompile("`([^`]*)`") -var re_quoter = regexp.MustCompile(`(?m:^&gt; (.*)\n?)`) +var re_quoter = regexp.MustCompile(`(?m:^&gt; (.*)(\n- ?(.*))?\n?)`) +var re_reciter = regexp.MustCompile(`(<cite><a href=".*?">)https://twitter.com/([^/]+)/.*?(</a></cite>)`) var re_link = regexp.MustCompile(`.?.?https?://[^\s"]+[\w/)!]`) var re_zerolink = regexp.MustCompile(`\[([^]]*)\]\(([^)]*\)?)\)`) var re_imgfix = regexp.MustCompile(`<img ([^>]*)>`)

@@ -77,7 +78,8 @@ s = re_link.ReplaceAllStringFunc(s, linkreplacer)

s = re_zerolink.ReplaceAllString(s, `<a href="$2">$1</a>`) s = re_bolder.ReplaceAllString(s, "$1<b>$2</b>$3") s = re_italicer.ReplaceAllString(s, "$1<i>$2</i>$3") - s = re_quoter.ReplaceAllString(s, "<blockquote>$1</blockquote><p>") + s = re_quoter.ReplaceAllString(s, "<blockquote>$1<br><cite>$3</cite></blockquote><p>") + s = re_reciter.ReplaceAllString(s, "$1$2$3") s = strings.Replace(s, "\n---\n", "<hr><p>", -1) s = re_lister.ReplaceAllStringFunc(s, func(m string) string {

@@ -117,6 +119,7 @@

// some final fixups s = strings.Replace(s, "\n", "<br>", -1) s = strings.Replace(s, "<br><blockquote>", "<blockquote>", -1) + s = strings.Replace(s, "<br><cite></cite>", "", -1) s = strings.Replace(s, "<br><pre>", "<pre>", -1) s = strings.Replace(s, "<br><ul>", "<ul>", -1) s = strings.Replace(s, "<p><br>", "<p>", -1)
M markitzero_test.gomarkitzero_test.go

@@ -1,6 +1,7 @@

package main import ( + "strings" "testing" )

@@ -106,3 +107,22 @@ - singleton`

output := `hello<ul><li>a list<li>the list</ul><p>para<ul><li>singleton</ul><p>` doonezerotest(t, input, output) } + +var benchData, simpleData string + +func init() { + benchData = strings.Repeat("hello there sir. It is a **fine** day for some testing!\n", 100) + simpleData = strings.Repeat("just a few words\n", 100) +} + +func BenchmarkModerateSize(b *testing.B) { + for n := 0; n < b.N; n++ { + markitzero(benchData) + } +} + +func BenchmarkSimpleData(b *testing.B) { + for n := 0; n < b.N; n++ { + markitzero(simpleData) + } +}
M schema.goschema.go

@@ -5,8 +5,8 @@

create table honks (honkid integer primary key, userid integer, what text, honker text, xid text, rid text, dt text, url text, audience text, noise text, convoy text, whofore integer, format text, precis text, oonker text, flags integer); create table donks (honkid integer, fileid integer); create table filemeta (fileid integer primary key, xid text, name text, description text, url text, media text, local integer); -create table honkers (honkerid integer primary key, userid integer, name text, xid text, flavor text, combos text, owner text); -create table xonkers (xonkerid integer primary key, name text, info text, flavor text); +create table honkers (honkerid integer primary key, userid integer, name text, xid text, flavor text, combos text, owner text, meta text); +create table xonkers (xonkerid integer primary key, name text, info text, flavor text, dt text); create table zonkers (zonkerid integer primary key, userid integer, name text, wherefore text); create table doovers(dooverid integer primary key, dt text, tries integer, userid integer, rcpt text, msg blob); create table onts (ontology text, honkid integer);
M schema.sqlschema.sql

@@ -2,8 +2,8 @@

create table honks (honkid integer primary key, userid integer, what text, honker text, xid text, rid text, dt text, url text, audience text, noise text, convoy text, whofore integer, format text, precis text, oonker text, flags integer); create table donks (honkid integer, fileid integer); create table filemeta (fileid integer primary key, xid text, name text, description text, url text, media text, local integer); -create table honkers (honkerid integer primary key, userid integer, name text, xid text, flavor text, combos text, owner text); -create table xonkers (xonkerid integer primary key, name text, info text, flavor text); +create table honkers (honkerid integer primary key, userid integer, name text, xid text, flavor text, combos text, owner text, meta text); +create table xonkers (xonkerid integer primary key, name text, info text, flavor text, dt text); create table zonkers (zonkerid integer primary key, userid integer, name text, wherefore text); create table doovers(dooverid integer primary key, dt text, tries integer, userid integer, rcpt text, msg blob); create table onts (ontology text, honkid integer);
M skulduggery.goskulduggery.go

@@ -21,86 +21,8 @@

"github.com/mattn/go-runewidth" ) -// these lists are mostly (?) accurate -var bigboldshitz = "๐€๐๐‚๐ƒ๐„๐…๐†๐‡๐ˆ๐‰๐Š๐‹๐Œ๐๐Ž๐๐๐‘๐’๐“๐”๐•๐–๐—๐˜๐™" -var lilboldshitz = "๐š๐›๐œ๐๐ž๐Ÿ๐ ๐ก๐ข๐ฃ๐ค๐ฅ๐ฆ๐ง๐จ๐ฉ๐ช๐ซ๐ฌ๐ญ๐ฎ๐ฏ๐ฐ๐ฑ๐ฒ๐ณ" -var moeboldshitz = "๐—”๐—•๐—–๐——๐—˜๐—™๐—š๐—›๐—œ๐—๐—ž๐—Ÿ๐— ๐—ก๐—ข๐—ฃ๐—ค๐—ฅ๐—ฆ๐—ง๐—จ๐—ฉ๐—ช๐—ซ๐—ฌ๐—ญ" -var morboldshitz = "๐—ฎ๐—ฏ๐—ฐ๐—ฑ๐—ฒ๐—ณ๐—ด๐—ต๐—ถ๐—ท๐—ธ๐—น๐—บ๐—ป๐—ผ๐—ฝ๐—พ๐—ฟ๐˜€๐˜๐˜‚๐˜ƒ๐˜„๐˜…๐˜†๐˜‡" -var biggothshitz = "๐•ฌ๐•ญ๐•ฎ๐•ฏ๐•ฐ๐•ฑ๐•ฒ๐•ณ๐•ด๐•ต๐•ถ๐•ท๐•ธ๐•น๐•บ๐•ป๐•ผ๐•ฝ๐•พ๐•ฟ๐–€๐–๐–‚๐–ƒ๐–„๐–…" -var lilgothshitz = "๐–†๐–‡๐–ˆ๐–‰๐–Š๐–‹๐–Œ๐–๐–Ž๐–๐–๐–‘๐–’๐–“๐–”๐–•๐––๐–—๐–˜๐–™๐–š๐–›๐–œ๐–๐–ž๐–Ÿ" -var moegothshitz = "๐”„๐”…๐•ฎ๐”‡๐”ˆ๐”‰๐”Š๐•ณโ„‘๐”๐”Ž๐”๐”๐”‘๐”’๐”“๐””โ„œ๐”–๐”—๐”˜๐”™๐”š๐”›๐”œ๐–…" -var morgothshitz = "๐”ž๐”Ÿ๐” ๐”ก๐”ข๐”ฃ๐”ค๐”ฅ๐”ฆ๐”ง๐”จ๐”ฉ๐”ช๐”ซ๐”ฌ๐”ญ๐”ฎ๐”ฏ๐”ฐ๐”ฑ๐”ฒ๐”ณ๐”ด๐”ต๐”ถ๐”ท" -var bigitalshitz = "๐‘จ๐‘ฉ๐‘ช๐‘ซ๐‘ฌ๐‘ญ๐‘ฎ๐‘ฏ๐‘ฐ๐‘ฑ๐‘ฒ๐‘ณ๐‘ด๐‘ต๐‘ถ๐‘ท๐‘ธ๐‘น๐‘บ๐‘ป๐‘ผ๐‘ฝ๐‘พ๐‘ฟ๐’€๐’" -var lilitalshitz = "๐’‚๐’ƒ๐’„๐’…๐’†๐’‡๐’ˆ๐’‰๐’Š๐’‹๐’Œ๐’๐’Ž๐’๐’๐’‘๐’’๐’“๐’”๐’•๐’–๐’—๐’˜๐’™๐’š๐’›" -var moeitalshitz = "๐˜ผ๐˜ฝ๐˜พ๐˜ฟ๐™€๐™๐™‚๐™ƒ๐™„๐™…๐™†๐™‡๐™ˆ๐™‰๐™Š๐™‹๐™Œ๐™๐™Ž๐™๐™๐™‘๐™’๐™“๐™”๐™•" -var moritalshitz = "๐™–๐™—๐™˜๐™™๐™š๐™›๐™œ๐™๐™ž๐™Ÿ๐™ ๐™ก๐™ข๐™ฃ๐™ค๐™ฅ๐™ฆ๐™ง๐™จ๐™ฉ๐™ช๐™ซ๐™ฌ๐™ญ๐™ฎ๐™ฏ" -var bigbangshitz = "๐”ธ๐”นโ„‚๐”ป๐”ผ๐”ฝ๐”พโ„๐•€๐•๐•‚๐•ƒ๐•„โ„•๐•†โ„™โ„šโ„๐•Š๐•‹๐•Œ๐•๐•Ž๐•๐•โ„ค" -var lilbangshitz = "๐•’๐•“๐•”๐••๐•–๐•—๐•˜๐•™๐•š๐•›๐•œ๐•๐•ž๐•Ÿ๐• ๐•ก๐•ข๐•ฃ๐•ค๐•ฅ๐•ฆ๐•ง๐•จ๐•ฉ๐•ช๐•ซ" -var bigwideshitz = "๏ผก๏ผข๏ผฃ๏ผค๏ผฅ๏ผฆ๏ผง๏ผจ๏ผฉ๏ผช๏ผซ๏ผฌ๏ผญ๏ผฎ๏ผฏ๏ผฐ๏ผฑ๏ผฒ๏ผณ๏ผด๏ผต๏ผถ๏ผท๏ผธ๏ผน๏ผบ" -var lilwideshitz = "๏ฝ๏ฝ‚๏ฝƒ๏ฝ„๏ฝ…๏ฝ†๏ฝ‡๏ฝˆ๏ฝ‰๏ฝŠ๏ฝ‹๏ฝŒ๏ฝ๏ฝŽ๏ฝ๏ฝ๏ฝ‘๏ฝ’๏ฝ“๏ฝ”๏ฝ•๏ฝ–๏ฝ—๏ฝ˜๏ฝ™๏ฝš" -var bigblokshitz = "๐Ÿ…ฐ๐Ÿ…ฑ๐Ÿ…ฒ๐Ÿ…ณ๐Ÿ…ด๐Ÿ…ต๐Ÿ…ถ๐Ÿ…ท๐Ÿ…ธ๐Ÿ…น๐Ÿ…บ๐Ÿ…ป๐Ÿ…ผ๐Ÿ…ฝ๐Ÿ…พ๐Ÿ…ฟ๐Ÿ†€๐Ÿ†๐Ÿ†‚๐Ÿ†ƒ๐Ÿ†„๐Ÿ†…๐Ÿ††๐Ÿ†‡๐Ÿ†ˆ๐Ÿ†‰" -var evenmoeshitz = "๐ด๐ต๐ถ๐ท๐ธ๐น๐บ๐ป๐ผ๐ฝ๐พ๐ฟ๐‘€๐‘๐‘‚๐‘ƒ๐‘„๐‘…๐‘†๐‘‡๐‘ˆ๐‘‰๐‘Š๐‘‹๐‘Œ๐‘" -var evenmorshitz = "๐’ถ๐’ท๐’ธ๐’น๐‘’๐’ป๐“ฐ๐’ฝ๐’พ๐’ฟ๐“€๐“๐“‚๐“ƒ๐“ธ๐“…๐“†๐“‡๐“ˆ๐“‰๐“Š๐“‹๐“Œ๐“๐“Ž๐“" - -var re_alltheshitz = regexp.MustCompile(`([` + - bigboldshitz + lilboldshitz + - moeboldshitz + morboldshitz + - biggothshitz + lilgothshitz + - moegothshitz + morgothshitz + - bigitalshitz + lilitalshitz + - moeitalshitz + moritalshitz + - bigbangshitz + lilbangshitz + - bigwideshitz + lilwideshitz + - evenmoeshitz + evenmorshitz + - bigblokshitz + - "][ '\ufe0f]?){3,}") - -var allUppers = []string{bigboldshitz, moeboldshitz, biggothshitz, bigwideshitz, moegothshitz, bigitalshitz, moeitalshitz, bigbangshitz, bigblokshitz, evenmoeshitz} -var allLowers = []string{lilboldshitz, morboldshitz, lilgothshitz, lilwideshitz, morgothshitz, lilitalshitz, moritalshitz, lilbangshitz, evenmorshitz} - var skinTones = "\U0001F3FB\U0001F3FC\U0001F3FD\U0001F3FE\U0001F3FF" var re_moredumb = regexp.MustCompile("[\U0001f44f\U0001f6a8\U000026a0][" + skinTones + "\ufe0f]*") - -// this may not be especially fast -func unpucker(s string) string { - fixer := func(r string) string { - x := make([]byte, len(r)) - xi := 0 - loop1: - for _, c := range r { - xi++ - if c == ' ' || c == '\'' { - x[xi] = byte(c) - continue - } - for _, set := range allUppers { - i := 0 - for _, rr := range set { - if rr == c { - x[xi] = byte('A' + i) - continue loop1 - } - i++ - } - } - for _, set := range allLowers { - i := 0 - for _, rr := range set { - if rr == c { - x[xi] = byte('a' + i) - continue loop1 - } - i++ - } - } - x[xi] = '.' - } - return string(x) - } - s = re_alltheshitz.ReplaceAllStringFunc(s, fixer) - - return demoji(s) -} func demoji(s string) string { s = re_moredumb.ReplaceAllString(s, ".")
M toys/Makefiletoys/Makefile

@@ -1,11 +1,14 @@

-all: gettoken saytheday youvegothonks +all: gettoken saytheday sprayandpray youvegothonks gettoken: gettoken.go go build gettoken.go saytheday: saytheday.go go build saytheday.go + +sprayandpray: sprayandpray.go + go build sprayandpray.go youvegothonks: youvegothonks.go go build youvegothonks.go
M toys/READMEtoys/README

@@ -4,6 +4,8 @@ A little of this, a little of that.

gettoken.go - obtains an authorization token -saytheday.go - posts a new honk +saytheday.go - posts a new honk that's a date based look and say sequence + +sprayandpray.go - send an activity with no error checking and hope it works youvegothonks.go - polls for new mesages
A toys/sprayandpray.go

@@ -0,0 +1,59 @@

+package main + +import ( + "flag" + "fmt" + "io/ioutil" + "log" + "net/http" + "net/url" + "os" + "strings" +) + + +func sendmsg(server, token, msg, rcpt string) { + form := make(url.Values) + form.Add("token", token) + form.Add("action", "sendactivity") + form.Add("msg", msg) + form.Add("rcpt", rcpt) + apiurl := fmt.Sprintf("https://%s/api", server) + req, err := http.NewRequest("POST", apiurl, strings.NewReader(form.Encode())) + if err != nil { + log.Fatal(err) + } + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + resp, err := http.DefaultClient.Do(req) + if err != nil { + log.Fatal(err) + } + defer resp.Body.Close() + answer, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Fatal(err) + } + if resp.StatusCode != 200 { + log.Fatalf("status: %d: %s", resp.StatusCode, answer) + } +} + +func main() { + var server, token, msgfile, rcpt string + flag.StringVar(&server, "server", server, "server to connnect") + flag.StringVar(&token, "token", token, "auth token to use") + flag.StringVar(&msgfile, "msgfile", token, "file with message to send") + flag.StringVar(&rcpt, "rcpt", rcpt, "rcpt to send it to") + flag.Parse() + + if server == "" || token == "" || msgfile == "" || rcpt == "" { + flag.Usage() + os.Exit(1) + } + msg, err := ioutil.ReadFile(msgfile) + if err != nil { + log.Fatal(err) + } + + sendmsg(server, token, string(msg), rcpt) +}
M unveil.gounveil.go

@@ -62,4 +62,7 @@ Unveil(dataDir, "rwc")

C.unveil(nil, nil) Pledge("stdio rpath wpath cpath flock dns inet unix") }) + backendhooks = append(backendhooks, func() { + Pledge("stdio unix") + }) }
M upgradedb.goupgradedb.go

@@ -24,7 +24,7 @@ "strings"

"time" ) -var myVersion = 32 +var myVersion = 34 func doordie(db *sql.DB, s string, args ...interface{}) { _, err := db.Exec(s, args...)

@@ -356,6 +356,16 @@ doordie(db, "create index idx_trackhonkid on tracks(xid)")

doordie(db, "update config set value = 32 where key = 'dbversion'") fallthrough case 32: + doordie(db, "alter table xonkers add column dt text") + doordie(db, "update xonkers set dt = ?", time.Now().UTC().Format(dbtimeformat)) + doordie(db, "update config set value = 33 where key = 'dbversion'") + fallthrough + case 33: + doordie(db, "alter table honkers add column meta text") + doordie(db, "update honkers set meta = '{}'") + doordie(db, "update config set value = 34 where key = 'dbversion'") + fallthrough + case 34: default: log.Fatalf("can't upgrade unknown version %d", dbversion)
M util.goutil.go

@@ -412,13 +412,8 @@ }

func setconfig(key string, val interface{}) error { db := opendatabase() + db.Exec("delete from config where key = ?", key) _, err := db.Exec("insert into config (key, value) values (?, ?)", key, val) - return err -} - -func updateconfig(key string, val interface{}) error { - db := opendatabase() - _, err := db.Exec("update config set value = ? where key = ?", val, key) return err }
M views/account.htmlviews/account.html

@@ -7,7 +7,7 @@ <div>

<form id="aboutform" action="/saveuser" method="POST"> <input type="hidden" name="CSRF" value="{{ .UserCSRF }}"> <p>about me: -<p><textarea name="whatabout">{{ .User.About }}</textarea> +<p><textarea name="whatabout">{{ .WhatAbout }}</textarea> <p><label class="button" for="skinny">skinny layout:</label> <input tabindex=1 type="checkbox" id="skinny" name="skinny" value="skinny" {{ if .User.Options.SkinnyCSS }}checked{{ end }}><span></span> <p><label class="button" for="maps">apple map links:</label>
M views/hfcs.htmlviews/hfcs.html

@@ -9,6 +9,9 @@ <hr>

<h3>new filter</h3> <p><label for="name">filter name:</label><br> <input tabindex=1 type="text" name="name" value="" autocomplete=off> +<p><label for="filtnotes">notes:</label><br> +<textarea tabindex=1 name="filtnotes" height=4> +</textarea> <hr> <h3>match</h3> <p><label for="actor">who or where:</label><br>

@@ -48,6 +51,7 @@ {{ $csrf := .FilterCSRF }}

{{ range .Filters }} <section class="honk"> <p>Name: {{ .Name }} +{{ with .Notes }}<p>Notes: {{ . }}{{ end }} <p>Date: {{ .Date.Format "2006-01-02" }} {{ with .Actor }}<p>Who: {{ . }}{{ end }} {{ with .IncludeAudience }} (inclusive) {{ end }} {{ with .Text }}<p>Text: {{ . }}{{ end }}
M views/honk.htmlviews/honk.html

@@ -66,15 +66,15 @@ {{ end }}

{{ range .Donks }} {{ if .Local }} {{ if eq .Media "text/plain" }} -<p><a href="/d/{{ .XID }}">Attachment: {{ .Name }}</a> {{ .Desc }} +<p><a href="/d/{{ .XID }}">Attachment: {{ .Name }}</a>{{ if not (eq .Desc .Name) }} {{ .Desc }}{{ end }} {{ else if eq .Media "application/pdf" }} -<p><a href="/d/{{ .XID }}">Attachment: {{ .Name }}</a> {{ .Desc }} +<p><a href="/d/{{ .XID }}">Attachment: {{ .Name }}</a>{{ if not (eq .Desc .Name) }} {{ .Desc }}{{ end }} {{ else }} <p><img src="/d/{{ .XID }}" title="{{ .Desc }}" alt="{{ .Desc }}"> {{ end }} {{ else }} {{ if .XID }} -<p><a href="{{ .URL }}" rel=noreferrer>External Attachment: {{ .Name }}</a> +<p><a href="{{ .URL }}" rel=noreferrer>External Attachment: {{ .Name }}</a>{{ if not (eq .Desc .Name) }} {{ .Desc }}{{ end }} {{ else }} {{ if eq .Media "video/mp4" }} <p><video controls src="{{ .URL }}">{{ .Name }}</video>
M views/honkers.htmlviews/honkers.html

@@ -13,6 +13,9 @@ <p><label for=combos>combos:</label><br>

<input tabindex=1 type="text" name="combos" value="" placeholder="optional"> <p><span><label class=button for="peep">skip subscribe: <input tabindex=1 type="checkbox" id="peep" name="peep" value="peep"><span></span></label></span> +<p><label for="notes">notes:</label><br> +<textarea tabindex=1 name="notes"> +</textarea> <p><button tabindex=1 name="add honker" value="add honker">add honker</button> </form> </div>

@@ -42,6 +45,8 @@ <form action="/submithonker" method="POST">

<input type="hidden" name="CSRF" value="{{ $honkercsrf }}"> <input type="hidden" name="honkerid" value="{{ .ID }}"> <p>name: <input type="text" name="name" value="{{ .Name }}"> +<p><label for="notes">notes:</label><br> +<textarea name="notes">{{ .Meta.Notes }}</textarea> <p>combos: <input type="text" name="combos" value="{{ range .Combos }}{{ . }} {{end}}"> {{ if eq .Flavor "sub" }} <p>unsub: <input type="text" name="goodbye" placeholder="press F" value="" autocomplete=off>
A views/pleroma.css

@@ -0,0 +1,7 @@

+html { + --bg-page: #1b2735; + --bg-dark: #121a24; + --fg: #b9b9ba; + --hl: #d8a070; + --fg-subtle: rgba(185, 185, 186, 0.5); +}
M views/style.cssviews/style.css

@@ -1,8 +1,9 @@

html { - --bg-page: #305; + --bg-page: #306; --bg-dark: #002; - --fg: #dde; - --fg-subtle: #aab; + --fg: #dcf; + --hl: #dcf; + --fg-subtle: #a9c; --fg-limited: #a79; }

@@ -23,6 +24,9 @@ margin-left: 0em;

padding-left: 0.5em; border-left: 1px solid var(--fg-subtle); } +blockquote cite { + margin-left: 2em; +} table { display: block; max-width: 100%;

@@ -73,7 +77,7 @@ }

header > details[open] { padding: 1em 1em 0em 1em; background: var(--bg-dark); - border: 1px solid var(--fg); + border: 1px solid var(--hl); margin-bottom: 1em; opacity: 1.0; }

@@ -99,9 +103,12 @@ max-width: 1200px;

margin: auto; font-size: 1.5em; } +hr { + border-color: var(--hl); +} .info { background: var(--bg-dark); - border: 1px solid var(--fg); + border: 1px solid var(--hl); margin-bottom: 1em; padding: 0em 1em 0em 1em; }

@@ -117,7 +124,7 @@ font-size: 16px;

font-family: monospace; color: var(--fg); background: var(--bg-page); - border: 1px solid var(--fg); + border: 1px solid var(--hl); padding: 0.5em; white-space: nowrap; }

@@ -140,11 +147,14 @@ font-size: 1em;

background: var(--bg-page); color: var(--fg); width: 600px; - height: 8em; + height: 4em; margin-bottom: 0.5em; box-sizing: border-box; max-width: 100%; } +textarea#honknoise { + height: 10em; +} input[type="checkbox"] { position: fixed; top: -9999px;

@@ -163,13 +173,13 @@ display: none;

} .glow { - box-shadow: 0px 0px 16px var(--fg); + box-shadow: 0px 0px 16px var(--hl); } .honk { margin: auto; background: var(--bg-dark); - border: 1px solid var(--fg); + border: 1px solid var(--hl); border-radius: 1em; margin-bottom: 1em; padding-left: 1em;
M web.goweb.go

@@ -48,6 +48,8 @@

var userSep = "u" var honkSep = "h" +var debugMode = false + func getuserstyle(u *login.UserInfo) template.CSS { if u == nil { return ""

@@ -223,8 +225,10 @@ if honk.Date.After(modtime) {

modtime = honk.Date } } - w.Header().Set("Cache-Control", "max-age=300") - w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat)) + if !debugMode { + w.Header().Set("Cache-Control", "max-age=300") + w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat)) + } err := feed.Write(w) if err != nil {

@@ -330,6 +334,10 @@ return

} keyname, err := httpsig.VerifyRequest(r, payload, zaggy) + if err != nil && keyname != "" { + savingthrow(keyname) + keyname, err = httpsig.VerifyRequest(r, payload, zaggy) + } if err != nil { log.Printf("inbox message failed signature for %s from %s", keyname, r.Header.Get("X-Forwarded-For")) if keyname != "" {

@@ -460,6 +468,10 @@ if crappola(j) {

return } keyname, err := httpsig.VerifyRequest(r, payload, zaggy) + if err != nil && keyname != "" { + savingthrow(keyname) + keyname, err = httpsig.VerifyRequest(r, payload, zaggy) + } if err != nil { log.Printf("inbox message failed signature for %s from %s", keyname, r.Header.Get("X-Forwarded-For")) if keyname != "" {

@@ -866,7 +878,7 @@ }

sort.Slice(onts, func(i, j int) bool { return onts[i].Name < onts[j].Name }) - if u == nil { + if u == nil && !debugMode { w.Header().Set("Cache-Control", "max-age=300") } templinfo := getInfo(r)

@@ -882,6 +894,33 @@ xid string

who string } +func getbacktracks(xid string) []string { + c := make(chan bool) + dumptracks <- c + <-c + row := stmtGetTracks.QueryRow(xid) + var rawtracks string + err := row.Scan(&rawtracks) + if err != nil { + if err != sql.ErrNoRows { + log.Printf("error scanning tracks: %s", err) + } + return nil + } + var rcpts []string + for _, f := range strings.Split(rawtracks, " ") { + idx := strings.LastIndexByte(f, '#') + if idx != -1 { + f = f[:idx] + } + if !strings.HasPrefix(f, "https://") { + f = fmt.Sprintf("%%https://%s/inbox", f) + } + rcpts = append(rcpts, f) + } + return rcpts +} + func savetracks(tracks map[string][]string) { db := opendatabase() tx, err := db.Begin()

@@ -932,6 +971,7 @@ log.Printf("saved %d new fetches", count)

} var trackchan = make(chan Track) +var dumptracks = make(chan chan bool) func tracker() { timeout := 4 * time.Minute

@@ -947,6 +987,11 @@ go savetracks(tracks)

tracks = make(map[string][]string) } sleeper.Reset(timeout) + case c := <-dumptracks: + if len(tracks) > 0 { + savetracks(tracks) + } + c <- true case <-endoftheworld: if len(tracks) > 0 { savetracks(tracks)

@@ -1052,7 +1097,7 @@ } else {

templinfo["TopHID"] = 0 } } - if u == nil { + if u == nil && !debugMode { w.Header().Set("Cache-Control", "max-age=60") } err := readviews.Execute(w, "honkpage.html", templinfo)

@@ -1077,6 +1122,17 @@ options.MapLink = "apple"

} else { options.MapLink = "" } + if ava := re_avatar.FindString(whatabout); ava != "" { + whatabout = re_avatar.ReplaceAllString(whatabout, "") + ava = ava[7:] + if ava[0] == ' ' { + ava = ava[1:] + } + options.Avatar = fmt.Sprintf("https://%s/meme/%s", serverName, ava) + } else { + options.Avatar = "" + } + whatabout = strings.TrimSpace(whatabout) j, err := jsonify(options) if err == nil { _, err = db.Exec("update users set about = ?, options = ? where username = ?", whatabout, j, u.Username)

@@ -1383,8 +1439,9 @@

noise = strings.Replace(noise, "\r", "", -1) noise = quickrename(noise, userinfo.UserID) noise = hooterize(noise) + honk.Mentions = bunchofgrapes(noise) honk.Noise = noise - translate(honk, false) + translate(honk) var convoy string if rid != "" {

@@ -1406,6 +1463,12 @@ break

} } honk.RID = rid + if xonk.Precis != "" && honk.Precis == "" { + honk.Precis = xonk.Precis + if !(strings.HasPrefix(honk.Precis, "DZ:") || strings.HasPrefix(honk.Precis, "re: re: re: ")) { + honk.Precis = "re: " + honk.Precis + } + } } else { honk.Audience = []string{thewholeworld} }

@@ -1516,14 +1579,6 @@ } else {

log.Printf("can't find file: %s", xid) } } - herd := herdofemus(noise) - for _, e := range herd { - donk := savedonk(e.ID, e.Name, e.Name, "image/png", true) - if donk != nil { - donk.Name = e.Name - honk.Donks = append(honk.Donks, donk) - } - } memetize(honk) imaginate(honk)

@@ -1664,6 +1719,10 @@ combos := strings.TrimSpace(r.FormValue("combos"))

combos = " " + combos + " " honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0) + var meta HonkerMeta + meta.Notes = strings.TrimSpace(r.FormValue("notes")) + mj, _ := jsonify(&meta) + defer honkerinvalidator.Clear(u.UserID) if honkerid > 0 {

@@ -1714,7 +1773,7 @@

http.Redirect(w, r, "/honkers", http.StatusSeeOther) return } - _, err := stmtUpdateHonker.Exec(name, combos, honkerid, u.UserID) + _, err := stmtUpdateHonker.Exec(name, combos, mj, honkerid, u.UserID) if err != nil { log.Printf("update honker err: %s", err) return

@@ -1738,7 +1797,7 @@ flavor = "peep"

if name == "" { name = url } - _, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos, url) + _, err := stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos, url, mj) if err != nil { log.Print(err) return

@@ -1770,7 +1829,7 @@

if name == "" { name = info.Name } - _, err = stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos, info.Owner) + _, err = stmtSaveHonker.Exec(u.UserID, name, url, flavor, combos, info.Owner, mj) if err != nil { log.Print(err) return

@@ -1827,6 +1886,7 @@ filt.Replace = strings.TrimSpace(r.FormValue("filtreplace"))

if dur := parseDuration(r.FormValue("filtduration")); dur > 0 { filt.Expiration = time.Now().UTC().Add(dur) } + filt.Notes = strings.TrimSpace(r.FormValue("filtnotes")) if filt.Actor == "" && filt.Text == "" && !filt.IsAnnounce { log.Printf("blank filter")

@@ -1853,6 +1913,11 @@ templinfo := getInfo(r)

templinfo["UserCSRF"] = login.GetCSRF("saveuser", r) templinfo["LogoutCSRF"] = login.GetCSRF("logout", r) templinfo["User"] = user + about := user.About + if ava := user.Options.Avatar; ava != "" { + about += "\n\navatar: " + ava[strings.LastIndexByte(ava, '/')+1:] + } + templinfo["WhatAbout"] = about err := readviews.Execute(w, "account.html", templinfo) if err != nil { log.Print(err)

@@ -1923,14 +1988,19 @@ return fmt.Sprintf("%d", secs)

} func avatate(w http.ResponseWriter, r *http.Request) { + if debugMode { + loadAvatarColors() + } n := r.FormValue("a") - a := avatar(n) + a := genAvatar(n) w.Header().Set("Cache-Control", "max-age="+somedays()) w.Write(a) } func serveasset(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Cache-Control", "max-age=7776000") + if !debugMode { + w.Header().Set("Cache-Control", "max-age=7776000") + } dir := viewDir if r.URL.Path == "/local.css" { dir = dataDir

@@ -1939,7 +2009,9 @@ http.ServeFile(w, r, dir+"/views"+r.URL.Path)

} func servehelp(w http.ResponseWriter, r *http.Request) { name := mux.Vars(r)["name"] - w.Header().Set("Cache-Control", "max-age=3600") + if !debugMode { + w.Header().Set("Cache-Control", "max-age=3600") + } http.ServeFile(w, r, viewDir+"/docs/"+name) } func servehtml(w http.ResponseWriter, r *http.Request) {

@@ -1951,7 +2023,7 @@ templinfo["HonkVersion"] = softwareVersion

if r.URL.Path == "/about" { templinfo["Sensors"] = getSensors() } - if u == nil { + if u == nil && !debugMode { w.Header().Set("Cache-Control", "max-age=60") } err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo)

@@ -1960,14 +2032,16 @@ log.Print(err)

} } func serveemu(w http.ResponseWriter, r *http.Request) { - xid := mux.Vars(r)["xid"] + emu := mux.Vars(r)["emu"] + w.Header().Set("Cache-Control", "max-age="+somedays()) - http.ServeFile(w, r, dataDir+"/emus/"+xid) + http.ServeFile(w, r, dataDir+"/emus/"+emu) } func servememe(w http.ResponseWriter, r *http.Request) { - xid := mux.Vars(r)["xid"] + meme := mux.Vars(r)["meme"] + w.Header().Set("Cache-Control", "max-age="+somedays()) - http.ServeFile(w, r, dataDir+"/memes/"+xid) + http.ServeFile(w, r, dataDir+"/memes/"+meme) } func servefile(w http.ResponseWriter, r *http.Request) {

@@ -2073,6 +2147,7 @@

func apihandler(w http.ResponseWriter, r *http.Request) { u := login.GetUserInfo(r) userid := u.UserID + user, _ := butwhatabout(u.Username) action := r.FormValue("action") wait, _ := strconv.ParseInt(r.FormValue("wait"), 10, 0) log.Printf("api request '%s' on behalf of %s", action, u.Username)

@@ -2110,6 +2185,13 @@ reverbolate(userid, honks)

j := junk.New() j["honks"] = honks j.Write(w) + case "sendactivity": + public := r.FormValue("public") == "1" + rcpts := boxuprcpts(user, r.Form["rcpt"], public) + msg := []byte(r.FormValue("msg")) + for rcpt := range rcpts { + go deliverate(0, userid, rcpt, msg) + } default: http.Error(w, "unknown action", http.StatusNotFound) return

@@ -2158,9 +2240,8 @@ go enditall()

go redeliverator() go tracker() - debug := false - getconfig("debug", &debug) - readviews = templates.Load(debug, + getconfig("debug", &debugMode) + readviews = templates.Load(debugMode, viewDir+"/views/honkpage.html", viewDir+"/views/honkfrags.html", viewDir+"/views/honkers.html",

@@ -2178,11 +2259,12 @@ viewDir+"/views/header.html",

viewDir+"/views/onts.html", viewDir+"/views/honkpage.js", ) - if !debug { + if !debugMode { assets := []string{viewDir + "/views/style.css", dataDir + "/views/local.css", viewDir + "/views/honkpage.js"} for _, s := range assets { savedassetparams[s] = getassetparam(s) } + loadAvatarColors() } for _, h := range preservehooks {

@@ -2214,8 +2296,8 @@ getters.HandleFunc("/a", avatate)

getters.HandleFunc("/o", thelistingoftheontologies) getters.HandleFunc("/o/{name:.+}", showontology) getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile) - getters.HandleFunc("/emu/{xid:[[:alnum:]_.-]+}", serveemu) - getters.HandleFunc("/meme/{xid:[[:alnum:]_.-]+}", servememe) + getters.HandleFunc("/emu/{emu:[^.]*[^/]+}", serveemu) + getters.HandleFunc("/meme/{meme:[^.]*[^/]+}", servememe) getters.HandleFunc("/.well-known/webfinger", fingerlicker) getters.HandleFunc("/server", serveractor)