diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..b5de9ba --- /dev/null +++ b/.hgignore @@ -0,0 +1,4 @@ +.*\.db +memes +emus +honk diff --git a/.hgtags b/.hgtags index be50023..7d0946f 100644 --- a/.hgtags +++ b/.hgtags @@ -25,3 +25,4 @@ b140f7a3216b820aa13f982e45ff42781d7a8f4a v0.8.2 8a2a90379bf60d425fec114ff88f5fd9806a4965 v0.8.2 808ef90260d5d81db1ec98fb8894588a3ac7b369 v0.8.3 3ada67b721e7e4a478d0effacde14f36dc16e1de v0.8.4 +2e9969df06ddab8fa07999e91437dda28ec058ae v0.8.5 diff --git a/README b/README index d28c597..d5804db 100644 --- a/README +++ b/README @@ -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. diff --git a/activity.go b/activity.go index c152eaa..578bfe5 100644 --- a/activity.go +++ b/activity.go @@ -19,6 +19,7 @@ import ( "bytes" "crypto/rsa" "database/sql" + "errors" "fmt" "html" "io" @@ -141,6 +142,22 @@ func GetJunkTimeout(url string, timeout time.Duration) (junk.Junk, error) { 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 @@ func savedonk(url string, name, desc, media string, localize bool) *Donk { xid := xfiltrate() data := []byte{} if localize { - resp, err := http.Get(url) + fn := func() (interface{}, error) { + return fetchsome(url) + } + ii, err := flightdeck.Call(url, fn) if err != nil { - log.Printf("error fetching %s: %s", url, err) localize = false goto saveit } - defer resp.Body.Close() - if resp.StatusCode != 200 { - 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 @@ func savedonk(url string, name, desc, media string, localize bool) *Donk { 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 @@ func needxonk(user *WhatAbout, x *Honk) bool { } 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 @@ var boxofboxes = cache.New(cache.Options{Filler: func(ident string) (*Box, bool) 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 @@ func xonksaver(user *WhatAbout, item junk.Junk, origin string) *Honk { } 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 @@ func xonksaver(user *WhatAbout, item junk.Junk, origin string) *Honk { 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 @@ func xonksaver(user *WhatAbout, item junk.Junk, origin string) *Honk { } 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 @@ func xonksaver(user *WhatAbout, item junk.Junk, origin string) *Honk { 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 @@ func xonksaver(user *WhatAbout, item junk.Junk, origin string) *Honk { 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 @@ func xonksaver(user *WhatAbout, item junk.Junk, origin string) *Honk { 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 @@ func xonksaver(user *WhatAbout, item junk.Junk, origin string) *Honk { convoy = currenttid } if convoy == "" { - convoy = "missing-" + xfiltrate() + convoy = "data:,missing-" + xfiltrate() currenttid = convoy } xonk.Convoy = convoy @@ -1008,10 +1033,11 @@ func jonkjonk(user *WhatAbout, h *Honk) (junk.Junk, junk.Junk) { 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 @@ func jonkjonk(user *WhatAbout, h *Honk) (junk.Junk, 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 @@ func gimmejonk(xid string) ([]byte, bool) { 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 { @@ -1198,6 +1234,9 @@ func honkworldwide(user *WhatAbout, honk *Honk) { rcpts[h.XID] = true } } + for _, f := range getbacktracks(honk.XID) { + rcpts[f] = true + } } for a := range rcpts { go deliverate(0, user.ID, a, msg) @@ -1317,7 +1356,8 @@ var handfull = cache.New(cache.Options{Filler: func(name string) (string, bool) 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 @@ func ingestpubkey(origin string, obj junk.Junk) { 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 @@ func ingestboxes(origin string, obj junk.Junk) { 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 @@ func ingesthandle(origin string, obj junk.Junk) { } 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) } diff --git a/admin.go b/admin.go index dba315b..17f5598 100644 --- a/admin.go +++ b/admin.go @@ -36,6 +36,9 @@ func adminscreen() { smcup := esc + "[?1049h" rmcup := esc + "[?1049l" + var avatarColors string + getconfig("avatarcolors", &avatarColors) + messages := []*struct { name string label string @@ -56,6 +59,11 @@ func adminscreen() { label: "login", text: string(loginMsg), }, + { + name: "avatarcolors", + label: "avatar colors (4 RGBA hex numbers)", + text: string(avatarColors), + }, } cursel := 0 @@ -239,7 +247,7 @@ func adminscreen() { stdout.Flush() } editing = false - updateconfig(m.name, m.text) + setconfig(m.name, m.text) hidecursor() drawscreen() } diff --git a/avatar.go b/avatar.go index 6233014..46bcdcd 100644 --- a/avatar.go +++ b/avatar.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 @@ func avatar(name string) []byte { 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] } } } diff --git a/backend.go b/backend.go index 5d61147..8ad3117 100644 --- a/backend.go +++ b/backend.go @@ -23,6 +23,7 @@ import ( "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 @@ func shrinkit(data []byte) (*image.Image, error) { return res.Image, nil } +var backendhooks []func() + func backendServer() { log.Printf("backend server running") shrinker := new(Shrinker) @@ -87,7 +94,7 @@ func backendServer() { if err != nil { log.Panicf("unable to register shrinker: %s", err) } - for _, h := range preservehooks { + for _, h := range backendhooks { h() } srv.Accept(lis) diff --git a/database.go b/database.go index 2402a46..c43d704 100644 --- a/database.go +++ b/database.go @@ -101,13 +101,16 @@ func gethonkers(userid int64) []*Honker { 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 @@ func gethonksbysearch(userid int64, q string, wanted int64) []*Honk { 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 @@ func gethonksbysearch(userid int64, q string, wanted int64) []*Honk { 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 @@ func donksforhonks(honks []*Honk) { 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 @@ func updatehonk(h *Honk) error { 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 @@ func deletehonk(honkid int64) error { 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 @@ func saveextras(tx *sql.Tx, h *Honk) error { 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 @@ func deleteextras(tx *sql.Tx, honkid int64) error { 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 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 @@ func preparetodie(db *sql.DB, s string) *sql.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 @@ func prepareStatements(db *sql.DB) { 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 @@ func prepareStatements(db *sql.DB) { 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 @@ func prepareStatements(db *sql.DB) { 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 = ?") } diff --git a/docs/changelog.txt b/docs/changelog.txt index 1afcf8d..b0a0cec 100644 --- a/docs/changelog.txt +++ b/docs/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 @@ changelog + 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 @@ changelog + "Bug" fixes. --- 0.8.1 +=== 0.8.1 ++ Make it easier to upgrade by decoupling data dir from ".". @@ -48,7 +80,7 @@ changelog 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 @@ changelog - 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 @@ changelog + 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 @@ changelog + What may be considered UI improvements. --- 0.7.4 +=== 0.7.4 + Ever more bug fixes. @@ -147,19 +179,19 @@ changelog + 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 @@ changelog + Add max-width for video tag. --- 0.6.0 and prior +=== 0.6.0 Sixy Delights -All records from this time of primitive development have been lost. +Most 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 diff --git a/docs/hfcs.1 b/docs/hfcs.1 index 80500a5..74a94df 100644 --- a/docs/hfcs.1 +++ b/docs/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 diff --git a/docs/honk.1 b/docs/honk.1 index 299802d..5c2115c 100644 --- a/docs/honk.1 +++ b/docs/honk.1 @@ -43,15 +43,18 @@ The field is required. Either of two forms are accepted, the user's handle (or webfinger) or their ActivityPub actor URL. -The -.Ar name -field is optional and will be automatically inferred. -Examples: +.Pp .Dl @user@example.social .Dl https://example.social/users/user .Pp +The +.Ar name +field is optional and will be automatically inferred. +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 @@ The following keywords are supported: 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 diff --git a/docs/honk.3 b/docs/honk.3 index c12600e..bfd524d 100644 --- a/docs/honk.3 +++ b/docs/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 diff --git a/docs/honk.8 b/docs/honk.8 index f5a85c6..0feaaa5 100644 --- a/docs/honk.8 +++ b/docs/honk.8 @@ -100,6 +100,8 @@ Displayed on the home page. 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 @@ file which is important to backup and restore. 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 @@ -197,6 +206,12 @@ 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. Clean things up a bit. .Bd -literal -offset indent diff --git a/docs/mandoc.css b/docs/mandoc.css index 02c7748..f0160b9 100644 --- a/docs/mandoc.css +++ b/docs/mandoc.css @@ -274,15 +274,6 @@ a.In { } 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) { diff --git a/fun.go b/fun.go index a2371ea..a87ec93 100644 --- a/fun.go +++ b/fun.go @@ -28,6 +28,7 @@ import ( "os" "regexp" "strings" + "time" "golang.org/x/net/html" "humungus.tedunangst.com/r/webs/cache" @@ -60,17 +61,18 @@ func reverbolate(userid int64, honks []*Honk) { 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 @@ func reverbolate(userid int64, honks []*Honk) { 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 @@ func reverbolate(userid int64, honks []*Honk) { } } } + if local { + var emu Emu + emucache.Get(e, &emu) + if emu.ID != "" { + return fmt.Sprintf(``, emu.Name, emu.ID) + } + } return e } h.Precis = re_emus.ReplaceAllStringFunc(h.Precis, emuxifier) @@ -189,7 +198,7 @@ func imaginate(honk *Honk) { htf.String(honk.Noise) } -func translate(honk *Honk, redoimages bool) { +func translate(honk *Honk) { if honk.Format == "html" { return } @@ -210,31 +219,31 @@ func translate(honk *Honk, redoimages bool) { 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++ - } - } - honk.Donks = honk.Donks[:j] - - honk.Noise = re_memes.ReplaceAllString(honk.Noise, "") - honk.Noise = ontologize(mentionize(honk.Noise)) - honk.Noise = strings.Replace(honk.Noise, ")https://twitter.com/([^/]+)/.*?()`) var re_link = regexp.MustCompile(`.?.?https?://[^\s"]+[\w/)!]`) var re_zerolink = regexp.MustCompile(`\[([^]]*)\]\(([^)]*\)?)\)`) var re_imgfix = regexp.MustCompile(`]*)>`) @@ -77,7 +78,8 @@ func markitzero(s string) string { s = re_zerolink.ReplaceAllString(s, `$1`) s = re_bolder.ReplaceAllString(s, "$1$2$3") s = re_italicer.ReplaceAllString(s, "$1$2$3") - s = re_quoter.ReplaceAllString(s, "
$1
") + s = re_quoter.ReplaceAllString(s, "
$1
$3
") + s = re_reciter.ReplaceAllString(s, "$1$2$3") s = strings.Replace(s, "\n---\n", "
", -1)
s = re_lister.ReplaceAllStringFunc(s, func(m string) string {
@@ -117,6 +119,7 @@ func markitzero(s string) string {
// some final fixups
s = strings.Replace(s, "\n", "
", -1)
s = strings.Replace(s, "
", "", -1) + s = strings.Replace(s, "
", "", -1) s = strings.Replace(s, "", "", -1) s = strings.Replace(s, "", "
@@ -42,6 +45,8 @@ function expandstuff() {", -1) s = strings.Replace(s, "
", "", -1) diff --git a/markitzero_test.go b/markitzero_test.go index 6cd176b..2f39fcb 100644 --- a/markitzero_test.go +++ b/markitzero_test.go @@ -1,6 +1,7 @@ package main import ( + "strings" "testing" ) @@ -106,3 +107,22 @@ para output := `hello
- a list
- the list
para
- singleton
` 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) + } +} diff --git a/schema.go b/schema.go index a78fb24..1d430bd 100644 --- a/schema.go +++ b/schema.go @@ -5,8 +5,8 @@ var sqlSchema = ` 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); diff --git a/schema.sql b/schema.sql index 0587f73..8f501fd 100644 --- a/schema.sql +++ b/schema.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); diff --git a/skulduggery.go b/skulduggery.go index a09a3df..c8f669c 100644 --- a/skulduggery.go +++ b/skulduggery.go @@ -21,87 +21,9 @@ import ( "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, ".") diff --git a/toys/Makefile b/toys/Makefile index c2c2f3b..dcf7319 100644 --- a/toys/Makefile +++ b/toys/Makefile @@ -1,5 +1,5 @@ -all: gettoken saytheday youvegothonks +all: gettoken saytheday sprayandpray youvegothonks gettoken: gettoken.go go build gettoken.go @@ -7,5 +7,8 @@ gettoken: gettoken.go saytheday: saytheday.go go build saytheday.go +sprayandpray: sprayandpray.go + go build sprayandpray.go + youvegothonks: youvegothonks.go go build youvegothonks.go diff --git a/toys/README b/toys/README index 9239b9a..6f98c43 100644 --- a/toys/README +++ b/toys/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 diff --git a/toys/sprayandpray.go b/toys/sprayandpray.go new file mode 100644 index 0000000..44ee302 --- /dev/null +++ b/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) +} diff --git a/unveil.go b/unveil.go index 4650ae5..efdba9b 100644 --- a/unveil.go +++ b/unveil.go @@ -62,4 +62,7 @@ func init() { C.unveil(nil, nil) Pledge("stdio rpath wpath cpath flock dns inet unix") }) + backendhooks = append(backendhooks, func() { + Pledge("stdio unix") + }) } diff --git a/upgradedb.go b/upgradedb.go index 828349a..7026d0b 100644 --- a/upgradedb.go +++ b/upgradedb.go @@ -24,7 +24,7 @@ import ( "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 @@ func upgradedb() { 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) diff --git a/util.go b/util.go index b57b75a..2e340c5 100644 --- a/util.go +++ b/util.go @@ -412,16 +412,11 @@ func getconfig(key string, value interface{}) error { 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 -} - func openListener() (net.Listener, error) { var listenAddr string err := getconfig("listenaddr", &listenAddr) diff --git a/views/account.html b/views/account.html index c6425eb..dc2f957 100644 --- a/views/account.html +++ b/views/account.html @@ -7,7 +7,7 @@
name: +
+combos: {{ if eq .Flavor "sub" }}
unsub: diff --git a/views/pleroma.css b/views/pleroma.css new file mode 100644 index 0000000..b25439a --- /dev/null +++ b/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); +} diff --git a/views/style.css b/views/style.css index ea0ba4a..0422d76 100644 --- a/views/style.css +++ b/views/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 @@ blockquote { 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 { 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 @@ main { 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 @@ label.button, button, select { 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 @@ textarea { 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 @@ input[type=file] { } .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; diff --git a/web.go b/web.go index 950623d..6ee8dc5 100644 --- a/web.go +++ b/web.go @@ -48,6 +48,8 @@ var readviews *templates.Template var userSep = "u" var honkSep = "h" +var debugMode = false + func getuserstyle(u *login.UserInfo) template.CSS { if u == nil { return "" @@ -223,8 +225,10 @@ func showrss(w http.ResponseWriter, r *http.Request) { 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 @@ func inbox(w http.ResponseWriter, r *http.Request) { } 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 @@ func serverinbox(w http.ResponseWriter, r *http.Request) { 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 @@ func thelistingoftheontologies(w http.ResponseWriter, r *http.Request) { 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 @@ type Track struct { 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 @@ func savetracks(tracks map[string][]string) { } var trackchan = make(chan Track) +var dumptracks = make(chan chan bool) func tracker() { timeout := 4 * time.Minute @@ -947,6 +987,11 @@ func tracker() { 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 @@ func honkpage(w http.ResponseWriter, u *login.UserInfo, honks []*Honk, templinfo 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 @@ func saveuser(w http.ResponseWriter, r *http.Request) { } 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 @@ func submithonk(w http.ResponseWriter, r *http.Request, isAPI bool) { 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 @@ func submithonk(w http.ResponseWriter, r *http.Request, isAPI bool) { } } 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 @@ func submithonk(w http.ResponseWriter, r *http.Request, isAPI bool) { 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 @@ func submithonker(w http.ResponseWriter, r *http.Request) { 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 @@ func submithonker(w http.ResponseWriter, r *http.Request) { 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 @@ func submithonker(w http.ResponseWriter, r *http.Request) { 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 @@ func submithonker(w http.ResponseWriter, r *http.Request) { 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 @@ func savehfcs(w http.ResponseWriter, r *http.Request) { 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 @@ func accountpage(w http.ResponseWriter, r *http.Request) { 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 @@ func somedays() string { } 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 @@ func serveasset(w http.ResponseWriter, r *http.Request) { } 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 @@ func servehtml(w http.ResponseWriter, r *http.Request) { 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 @@ func servehtml(w http.ResponseWriter, r *http.Request) { } } 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 honkhonkline() { 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 @@ func apihandler(w http.ResponseWriter, r *http.Request) { 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 @@ func serve() { 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 @@ func serve() { 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 @@ func serve() { 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)