Ted Unangst 4 years ago
commit 5de0395059

@ -0,0 +1,4 @@
.*\.db
memes
emus
honk

@ -25,3 +25,4 @@ b140f7a3216b820aa13f982e45ff42781d7a8f4a v0.8.2
8a2a90379bf60d425fec114ff88f5fd9806a4965 v0.8.2
808ef90260d5d81db1ec98fb8894588a3ac7b369 v0.8.3
3ada67b721e7e4a478d0effacde14f36dc16e1de v0.8.4
2e9969df06ddab8fa07999e91437dda28ec058ae v0.8.5

@ -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.

@ -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)
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 @@ 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)
}

@ -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()
}

@ -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]
}
}
}

@ -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)

@ -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 = ?")
}

@ -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
Most records from this time of primitive development have been lost.
=== 0.5.0 Halfway to Heaven
=== 0.4.0 Fore Score
All records from this time of primitive development have been lost.
=== 0.3.0 Valorous Varaha

@ -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

@ -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

@ -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

@ -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

@ -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) {

124
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(`<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 @@ 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++
}
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 @@ func ontologies(s string) []string {
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 @@ func bunchofgrapes(s string) []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 @@ type Emu struct {
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 @@ func memetize(honk *Honk) {
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 @@ func quickrename(s string, userid int64) string {
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]
}
@ -613,6 +627,12 @@ func zaggy(keyname string) *rsa.PublicKey {
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 {
hash := strings.IndexByte(keyname, '#')
if hash == -1 {

@ -44,6 +44,7 @@ type Filter struct {
re_rewrite *regexp.Regexp
Replace string `json:",omitempty"`
Expiration time.Time
Notes string
}
type filtType uint
@ -168,15 +169,24 @@ func getfilters(userid int64, scope filtType) []*Filter {
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)
return true
@ -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 @@ func stealthmode(userid int64, r *http.Request) bool {
}
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 @@ func matchfilter(h *Honk, f *Filter) bool {
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 skipMedia(xonk *Honk) bool {
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
}

@ -88,6 +88,12 @@ type Honk struct {
Onts []string
Place *Place
Time *Time
Mentions []Mention
}
type Mention struct {
Who string
Where string
}
type OldRevision struct {
@ -173,6 +179,11 @@ type Honker struct {
Handle string
Flavor string
Combos []string
Meta HonkerMeta
}
type HonkerMeta struct {
Notes string
}
type SomeThing struct {
@ -199,6 +210,13 @@ var loginMsg template.HTML
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 @@ func main() {
}
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 @@ func main() {
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")

@ -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 @@ func markitzero(s string) string {
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 @@ func markitzero(s string) string {
// 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)

@ -1,6 +1,7 @@
package main
import (
"strings"
"testing"
)
@ -106,3 +107,22 @@ para
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)
}
}

@ -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);

@ -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);

@ -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, ".")

@ -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

@ -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

@ -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)
}

@ -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")
})
}

@ -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)

@ -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)

@ -7,7 +7,7 @@
<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>

@ -9,6 +9,9 @@ Honk Filtering and Censorship System
<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 @@ Honk Filtering and Censorship System
{{ 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 }}

@ -66,15 +66,15 @@ in reply to: <a href="{{ .RID }}" rel=noreferrer>{{ .RID }}</a>
{{ 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>

@ -13,6 +13,9 @@
<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 @@ function expandstuff() {
<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>

@ -0,0 +1,7 @@
html {
--bg-page: #1b2735;
--bg-dark: #121a24;
--fg: #b9b9ba;
--hl: #d8a070;
--fg-subtle: rgba(185, 185, 186, 0.5);
}

@ -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;

142
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)

Loading…
Cancel
Save