This commit is contained in:
Ted Unangst 2019-12-10 18:12:29 -05:00
commit 5de0395059
35 changed files with 735 additions and 319 deletions

4
.hgignore Normal file
View File

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

View File

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

4
README
View File

@ -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 may be made on public property only when the person doing
the honk has the permission of the owner of that property. the honk has the permission of the owner of that property.
-- disclaimer
Do not use honk to contact emergency services.

View File

@ -19,6 +19,7 @@ import (
"bytes" "bytes"
"crypto/rsa" "crypto/rsa"
"database/sql" "database/sql"
"errors"
"fmt" "fmt"
"html" "html"
"io" "io"
@ -141,6 +142,22 @@ func GetJunkTimeout(url string, timeout time.Duration) (junk.Junk, error) {
return j, nil 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 { func savedonk(url string, name, desc, media string, localize bool) *Donk {
if url == "" { if url == "" {
return nil return nil
@ -154,22 +171,16 @@ func savedonk(url string, name, desc, media string, localize bool) *Donk {
xid := xfiltrate() xid := xfiltrate()
data := []byte{} data := []byte{}
if localize { if localize {
resp, err := http.Get(url) fn := func() (interface{}, error) {
return fetchsome(url)
}
ii, err := flightdeck.Call(url, fn)
if err != nil { if err != nil {
log.Printf("error fetching %s: %s", url, err)
localize = false localize = false
goto saveit goto saveit
} }
defer resp.Body.Close() data = ii.([]byte)
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 = buf.Bytes()
if len(data) == 10*1024*1024 { if len(data) == 10*1024*1024 {
log.Printf("truncation likely") log.Printf("truncation likely")
} }
@ -188,6 +199,12 @@ func savedonk(url string, name, desc, media string, localize bool) *Donk {
format = "jpg" format = "jpg"
} }
xid = xid + "." + format 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 { } else if len(data) > 100000 {
log.Printf("not saving large attachment") log.Printf("not saving large attachment")
localize = false localize = false
@ -224,11 +241,20 @@ func needxonk(user *WhatAbout, x *Honk) bool {
} }
return needxonkid(user, x.XID) return needxonkid(user, x.XID)
} }
func needbonkid(user *WhatAbout, xid string) bool {
return needxonkidX(user, xid, true)
}
func needxonkid(user *WhatAbout, xid string) bool { 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+"/") { if strings.HasPrefix(xid, user.URL+"/") {
return false return false
} }
if rejectorigin(user.ID, xid) { if rejectorigin(user.ID, xid, isannounce) {
return false return false
} }
if iszonked(user.ID, xid) { 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) err := row.Scan(&info)
if err != nil { if err != nil {
log.Printf("need to get boxes for %s", ident) log.Printf("need to get boxes for %s", ident)
j, err := GetJunk(ident) var j junk.Junk
j, err = GetJunk(ident)
if err != nil { if err != nil {
log.Printf("error getting boxes: %s", err) log.Printf("error getting boxes: %s", err)
return nil, false return nil, false
@ -483,7 +510,7 @@ func xonksaver(user *WhatAbout, item junk.Junk, origin string) *Honk {
} else { } else {
xid, _ = item.GetString("object") xid, _ = item.GetString("object")
} }
if !needxonkid(user, xid) { if !needbonkid(user, xid) {
return nil return nil
} }
log.Printf("getting bonk: %s", xid) 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 = append(xonk.Audience, xonk.Honker)
xonk.Audience = oneofakind(xonk.Audience) xonk.Audience = oneofakind(xonk.Audience)
var mentions []string var mentions []Mention
if obj != nil { if obj != nil {
ot, _ := obj.GetString("type") ot, _ := obj.GetString("type")
url, _ = obj.GetString("url") url, _ = obj.GetString("url")
@ -697,7 +724,8 @@ func xonksaver(user *WhatAbout, item junk.Junk, origin string) *Honk {
} else if at == "Document" || at == "Image" { } else if at == "Document" || at == "Image" {
mt = strings.ToLower(mt) mt = strings.ToLower(mt)
log.Printf("attachment: %s %s", mt, u) 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 localize = true
} }
} else { } else {
@ -754,7 +782,9 @@ func xonksaver(user *WhatAbout, item junk.Junk, origin string) *Honk {
xonk.Place = p xonk.Place = p
} }
if tt == "Mention" { if tt == "Mention" {
m, _ := tag.GetString("href") var m Mention
m.Who, _ = tag.GetString("name")
m.Where, _ = tag.GetString("href")
mentions = append(mentions, m) mentions = append(mentions, m)
} }
} }
@ -833,8 +863,9 @@ func xonksaver(user *WhatAbout, item junk.Junk, origin string) *Honk {
xonk.Precis = precis xonk.Precis = precis
xonk.Format = "html" xonk.Format = "html"
xonk.Convoy = convoy xonk.Convoy = convoy
xonk.Mentions = mentions
for _, m := range mentions { for _, m := range mentions {
if m == user.URL { if m.Where == user.URL {
xonk.Whofore = 1 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) log.Printf("didn't find old version for update: %s", xonk.XID)
isUpdate = false isUpdate = false
} else { } else {
prev.Noise = xonk.Noise xonk.ID = prev.ID
prev.Precis = xonk.Precis updatehonk(&xonk)
prev.Date = xonk.Date
prev.Donks = xonk.Donks
prev.Onts = xonk.Onts
prev.Place = xonk.Place
prev.Whofore = xonk.Whofore
updatehonk(prev)
} }
} }
if !isUpdate && needxonk(user, &xonk) { if !isUpdate && needxonk(user, &xonk) {
@ -883,7 +908,7 @@ func xonksaver(user *WhatAbout, item junk.Junk, origin string) *Honk {
convoy = currenttid convoy = currenttid
} }
if convoy == "" { if convoy == "" {
convoy = "missing-" + xfiltrate() convoy = "data:,missing-" + xfiltrate()
currenttid = convoy currenttid = convoy
} }
xonk.Convoy = convoy xonk.Convoy = convoy
@ -1008,10 +1033,11 @@ func jonkjonk(user *WhatAbout, h *Honk) (junk.Junk, junk.Junk) {
jo["directMessage"] = true jo["directMessage"] = true
} }
mentions := bunchofgrapes(h.Noise) mentions := bunchofgrapes(h.Noise)
translate(h, true) translate(h)
redoimages(h)
jo["summary"] = html.EscapeString(h.Precis) jo["summary"] = html.EscapeString(h.Precis)
jo["content"] = h.Noise jo["content"] = h.Noise
if strings.HasPrefix(h.Precis, "DZ:") { if h.Precis != "" {
jo["sensitive"] = true jo["sensitive"] = true
} }
@ -1031,8 +1057,8 @@ func jonkjonk(user *WhatAbout, h *Honk) (junk.Junk, junk.Junk) {
for _, m := range mentions { for _, m := range mentions {
t := junk.New() t := junk.New()
t["type"] = "Mention" t["type"] = "Mention"
t["name"] = m.who t["name"] = m.Who
t["href"] = m.where t["href"] = m.Where
tags = append(tags, t) tags = append(tags, t)
} }
for _, o := range h.Onts { for _, o := range h.Onts {
@ -1167,24 +1193,34 @@ func gimmejonk(xid string) ([]byte, bool) {
return j, ok return j, ok
} }
func honkworldwide(user *WhatAbout, honk *Honk) { func boxuprcpts(user *WhatAbout, addresses []string, useshared bool) map[string]bool {
jonk, _ := jonkjonk(user, honk)
jonk["@context"] = itiswhatitis
msg := jonk.ToBytes()
rcpts := make(map[string]bool) rcpts := make(map[string]bool)
for _, a := range honk.Audience { for _, a := range addresses {
if a == thewholeworld || a == user.URL || strings.HasSuffix(a, "/followers") { if a == "" || a == thewholeworld || a == user.URL || strings.HasSuffix(a, "/followers") {
continue
}
if a[0] == '%' {
rcpts[a] = true
continue continue
} }
var box *Box var box *Box
ok := boxofboxes.Get(a, &box) ok := boxofboxes.Get(a, &box)
if ok && honk.Public && box.Shared != "" { if ok && useshared && box.Shared != "" {
rcpts["%"+box.Shared] = true rcpts["%"+box.Shared] = true
} else { } else {
rcpts[a] = true 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 { if honk.Public {
for _, h := range getdubs(user.ID) { for _, h := range getdubs(user.ID) {
if h.XID == user.URL { if h.XID == user.URL {
@ -1198,6 +1234,9 @@ func honkworldwide(user *WhatAbout, honk *Honk) {
rcpts[h.XID] = true rcpts[h.XID] = true
} }
} }
for _, f := range getbacktracks(honk.XID) {
rcpts[f] = true
}
} }
for a := range rcpts { for a := range rcpts {
go deliverate(0, user.ID, a, msg) 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") rel, _ := l.GetString("rel")
t, _ := l.GetString("type") t, _ := l.GetString("type")
if rel == "self" && friendorfoe(t) { 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 { if err != nil {
log.Printf("error saving fishname: %s", err) 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) log.Printf("error decoding %s pubkey: %s", keyname, err)
return return
} }
_, err = stmtSaveXonker.Exec(keyname, data, "pubkey") when := time.Now().UTC().Format(dbtimeformat)
_, err = stmtSaveXonker.Exec(keyname, data, "pubkey", when)
if err != nil { if err != nil {
log.Printf("error saving key: %s", err) log.Printf("error saving key: %s", err)
} }
@ -1455,8 +1496,9 @@ func ingestboxes(origin string, obj junk.Junk) {
outbox, _ := obj.GetString("outbox") outbox, _ := obj.GetString("outbox")
sbox, _ := obj.GetString("endpoints", "sharedInbox") sbox, _ := obj.GetString("endpoints", "sharedInbox")
if inbox != "" { if inbox != "" {
when := time.Now().UTC().Format(dbtimeformat)
m := strings.Join([]string{inbox, outbox, sbox}, " ") m := strings.Join([]string{inbox, outbox, sbox}, " ")
_, err = stmtSaveXonker.Exec(ident, m, "boxes") _, err = stmtSaveXonker.Exec(ident, m, "boxes", when)
if err != nil { if err != nil {
log.Printf("error saving boxes: %s", err) log.Printf("error saving boxes: %s", err)
} }
@ -1479,7 +1521,8 @@ func ingesthandle(origin string, obj junk.Junk) {
} }
handle, _ = obj.GetString("preferredUsername") handle, _ = obj.GetString("preferredUsername")
if handle != "" { if handle != "" {
_, err = stmtSaveXonker.Exec(xid, handle, "handle") when := time.Now().UTC().Format(dbtimeformat)
_, err = stmtSaveXonker.Exec(xid, handle, "handle", when)
if err != nil { if err != nil {
log.Printf("error saving handle: %s", err) log.Printf("error saving handle: %s", err)
} }

View File

@ -36,6 +36,9 @@ func adminscreen() {
smcup := esc + "[?1049h" smcup := esc + "[?1049h"
rmcup := esc + "[?1049l" rmcup := esc + "[?1049l"
var avatarColors string
getconfig("avatarcolors", &avatarColors)
messages := []*struct { messages := []*struct {
name string name string
label string label string
@ -56,6 +59,11 @@ func adminscreen() {
label: "login", label: "login",
text: string(loginMsg), text: string(loginMsg),
}, },
{
name: "avatarcolors",
label: "avatar colors (4 RGBA hex numbers)",
text: string(avatarColors),
},
} }
cursel := 0 cursel := 0
@ -239,7 +247,7 @@ func adminscreen() {
stdout.Flush() stdout.Flush()
} }
editing = false editing = false
updateconfig(m.name, m.text) setconfig(m.name, m.text)
hidecursor() hidecursor()
drawscreen() drawscreen()
} }

View File

@ -16,13 +16,52 @@
package main package main
import ( import (
"bufio"
"bytes" "bytes"
"crypto/sha512" "crypto/sha512"
"image" "image"
"image/png" "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 := sha512.New()
h.Write([]byte(name)) h.Write([]byte(name))
s := h.Sum(nil) s := h.Sum(nil)
@ -33,25 +72,25 @@ func avatar(name string) []byte {
xx := i/16*16 + j/16 xx := i/16*16 + j/16
x := s[xx] x := s[xx]
if x < 64 { if x < 64 {
img.Pix[p+0] = 16 img.Pix[p+0] = avatarcolors[0][0]
img.Pix[p+1] = 0 img.Pix[p+1] = avatarcolors[0][1]
img.Pix[p+2] = 48 img.Pix[p+2] = avatarcolors[0][2]
img.Pix[p+3] = 255 img.Pix[p+3] = avatarcolors[0][3]
} else if x < 128 { } else if x < 128 {
img.Pix[p+0] = 48 img.Pix[p+0] = avatarcolors[1][0]
img.Pix[p+1] = 0 img.Pix[p+1] = avatarcolors[1][1]
img.Pix[p+2] = 96 img.Pix[p+2] = avatarcolors[1][2]
img.Pix[p+3] = 255 img.Pix[p+3] = avatarcolors[1][3]
} else if x < 192 { } else if x < 192 {
img.Pix[p+0] = 72 img.Pix[p+0] = avatarcolors[2][0]
img.Pix[p+1] = 0 img.Pix[p+1] = avatarcolors[2][1]
img.Pix[p+2] = 144 img.Pix[p+2] = avatarcolors[2][2]
img.Pix[p+3] = 255 img.Pix[p+3] = avatarcolors[2][3]
} else { } else {
img.Pix[p+0] = 96 img.Pix[p+0] = avatarcolors[3][0]
img.Pix[p+1] = 0 img.Pix[p+1] = avatarcolors[3][1]
img.Pix[p+2] = 192 img.Pix[p+2] = avatarcolors[3][2]
img.Pix[p+3] = 255 img.Pix[p+3] = avatarcolors[3][3]
} }
} }
} }

View File

@ -23,6 +23,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"humungus.tedunangst.com/r/webs/gate"
"humungus.tedunangst.com/r/webs/image" "humungus.tedunangst.com/r/webs/image"
) )
@ -38,7 +39,11 @@ type ShrinkerResult struct {
Image *image.Image Image *image.Image
} }
var shrinkgate = gate.NewLimiter(4)
func (s *Shrinker) Shrink(args *ShrinkerArgs, res *ShrinkerResult) error { func (s *Shrinker) Shrink(args *ShrinkerArgs, res *ShrinkerResult) error {
shrinkgate.Start()
defer shrinkgate.Finish()
img, err := image.Vacuum(bytes.NewReader(args.Buf), args.Params) img, err := image.Vacuum(bytes.NewReader(args.Buf), args.Params)
if err != nil { if err != nil {
return err return err
@ -68,6 +73,8 @@ func shrinkit(data []byte) (*image.Image, error) {
return res.Image, nil return res.Image, nil
} }
var backendhooks []func()
func backendServer() { func backendServer() {
log.Printf("backend server running") log.Printf("backend server running")
shrinker := new(Shrinker) shrinker := new(Shrinker)
@ -87,7 +94,7 @@ func backendServer() {
if err != nil { if err != nil {
log.Panicf("unable to register shrinker: %s", err) log.Panicf("unable to register shrinker: %s", err)
} }
for _, h := range preservehooks { for _, h := range backendhooks {
h() h()
} }
srv.Accept(lis) srv.Accept(lis)

View File

@ -101,13 +101,16 @@ func gethonkers(userid int64) []*Honker {
var honkers []*Honker var honkers []*Honker
for rows.Next() { for rows.Next() {
h := new(Honker) h := new(Honker)
var combos string var combos, meta string
err = rows.Scan(&h.ID, &h.UserID, &h.Name, &h.XID, &h.Flavor, &combos) err = rows.Scan(&h.ID, &h.UserID, &h.Name, &h.XID, &h.Flavor, &combos, &meta)
h.Combos = strings.Split(strings.TrimSpace(combos), " ") if err == nil {
err = unjsonify(meta, &h.Meta)
}
if err != nil { if err != nil {
log.Printf("error scanning honker: %s", err) log.Printf("error scanning honker: %s", err)
return nil continue
} }
h.Combos = strings.Split(strings.TrimSpace(combos), " ")
honkers = append(honkers, h) honkers = append(honkers, h)
} }
return honkers return honkers
@ -249,8 +252,10 @@ func gethonksbysearch(userid int64, q string, wanted int64) []*Honk {
withhonker := 0 withhonker := 0
site := "" site := ""
withsite := 0 withsite := 0
withnotq := 0
terms := strings.Split(q, " ") terms := strings.Split(q, " ")
q = "%" q = "%"
notq := "%"
for _, t := range terms { for _, t := range terms {
if strings.HasPrefix(t, "site:") { if strings.HasPrefix(t, "site:") {
site = t[5:] site = t[5:]
@ -267,13 +272,27 @@ func gethonksbysearch(userid int64, q string, wanted int64) []*Honk {
withhonker = 1 withhonker = 1
continue continue
} }
if t[0] == '-' {
if t == "-" {
continue
}
if len(notq) != 1 {
notq += " "
}
notq += t[1:]
continue
}
if len(q) != 1 { if len(q) != 1 {
q += " " q += " "
} }
q += t q += t
} }
q += "%" 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) honks := getsomehonks(rows, err)
return honks return honks
} }
@ -413,6 +432,12 @@ func donksforhonks(honks []*Honk) {
continue continue
} }
h.Time = t h.Time = t
case "mentions":
err = unjsonify(j, &h.Mentions)
if err != nil {
log.Printf("error parsing mentions: %s", err)
continue
}
case "oldrev": case "oldrev":
default: default:
log.Printf("unknown meta genus: %s", genus) log.Printf("unknown meta genus: %s", genus)
@ -491,7 +516,7 @@ func updatehonk(h *Honk) error {
return err return err
} }
err = deleteextras(tx, h.ID) err = deleteextras(tx, h.ID, false)
if err == nil { if err == nil {
_, err = tx.Stmt(stmtUpdateHonk).Exec(h.Precis, h.Noise, h.Format, h.Whofore, dt, h.ID) _, 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 return err
} }
err = deleteextras(tx, honkid) err = deleteextras(tx, honkid, true)
if err == nil {
_, err = tx.Stmt(stmtDeleteMeta).Exec(honkid, "nonsense")
}
if err == nil { if err == nil {
_, err = tx.Stmt(stmtDeleteHonk).Exec(honkid) _, err = tx.Stmt(stmtDeleteHonk).Exec(honkid)
} }
@ -580,10 +602,20 @@ func saveextras(tx *sql.Tx, h *Honk) error {
return err return err
} }
} }
if m := h.Mentions; len(m) > 0 {
j, err := jsonify(m)
if err == nil {
_, err = tx.Stmt(stmtSaveMeta).Exec(h.ID, "mentions", j)
}
if err != nil {
log.Printf("error saving mentions: %s", err)
return err
}
}
return nil 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) _, err := tx.Stmt(stmtDeleteDonks).Exec(honkid)
if err != nil { if err != nil {
return err return err
@ -592,7 +624,11 @@ func deleteextras(tx *sql.Tx, honkid int64) error {
if err != nil { if err != nil {
return err 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 { if err != nil {
return err return err
} }
@ -691,8 +727,10 @@ var stmtAddDoover, stmtGetDoovers, stmtLoadDoover, stmtZapDoover, stmtOneHonker
var stmtUntagged, stmtDeleteHonk, stmtDeleteDonks, stmtDeleteOnts, stmtSaveZonker *sql.Stmt var stmtUntagged, stmtDeleteHonk, stmtDeleteDonks, stmtDeleteOnts, stmtSaveZonker *sql.Stmt
var stmtGetZonkers, stmtRecentHonkers, stmtGetXonker, stmtSaveXonker, stmtDeleteXonker *sql.Stmt var stmtGetZonkers, stmtRecentHonkers, stmtGetXonker, stmtSaveXonker, stmtDeleteXonker *sql.Stmt
var stmtAllOnts, stmtSaveOnt, stmtUpdateFlags, stmtClearFlags *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 stmtHonksISaved, stmtGetFilters, stmtSaveFilter, stmtDeleteFilter *sql.Stmt
var stmtGetTracks *sql.Stmt
func preparetodie(db *sql.DB, s string) *sql.Stmt { func preparetodie(db *sql.DB, s string) *sql.Stmt {
stmt, err := db.Prepare(s) stmt, err := db.Prepare(s)
@ -703,10 +741,10 @@ func preparetodie(db *sql.DB, s string) *sql.Stmt {
} }
func prepareStatements(db *sql.DB) { 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") 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) values (?, ?, ?, ?, ?, ?)") 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 = ?") 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 = ?") 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'") 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'") 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) 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) 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) 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) 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) 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 (?, ?, ?)") 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") 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 = ?") stmtDeleteHonk = preparetodie(db, "delete from honks where honkid = ?")
stmtUpdateHonk = preparetodie(db, "update honks set precis = ?, noise = ?, format = ?, whofore = ?, dt = ? 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'") stmtGetZonkers = preparetodie(db, "select zonkerid, name, wherefore from zonkers where userid = ? and wherefore <> 'zonk'")
stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)") stmtSaveZonker = preparetodie(db, "insert into zonkers (userid, name, wherefore) values (?, ?, ?)")
stmtGetXonker = preparetodie(db, "select info from xonkers where name = ? and flavor = ?") stmtGetXonker = preparetodie(db, "select info from xonkers where name = ? and flavor = ?")
stmtSaveXonker = preparetodie(db, "insert into xonkers (name, info, flavor) values (?, ?, ?)") stmtSaveXonker = preparetodie(db, "insert into xonkers (name, info, flavor, dt) values (?, ?, ?, ?)")
stmtDeleteXonker = preparetodie(db, "delete from xonkers where name = ? and flavor = ?") 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") 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 = ?") stmtUpdateFlags = preparetodie(db, "update honks set flags = flags | ? where honkid = ?")
stmtClearFlags = 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 = ?") stmtGetFilters = preparetodie(db, "select hfcsid, json from hfcs where userid = ?")
stmtSaveFilter = preparetodie(db, "insert into hfcs (userid, json) values (?, ?)") stmtSaveFilter = preparetodie(db, "insert into hfcs (userid, json) values (?, ?)")
stmtDeleteFilter = preparetodie(db, "delete from hfcs where userid = ? and hfcsid = ?") stmtDeleteFilter = preparetodie(db, "delete from hfcs where userid = ? and hfcsid = ?")
stmtGetTracks = preparetodie(db, "select fetches from tracks where xid = ?")
} }

View File

@ -1,6 +1,38 @@
changelog 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. + deluser command.
@ -10,17 +42,17 @@ changelog
+ Can never seem to version the changelog correctly. + Can never seem to version the changelog correctly.
-- 0.8.4 === 0.8.4
+ Fix bug preventing import of keys + Fix bug preventing import of keys
+ Option to switch map links to Apple. + Option to switch map links to Apple.
-- 0.8.3 === 0.8.3
- mistag. - mistag.
-- 0.8.2 === 0.8.2 Game Warden
++ Import command to preserve those embarssassing old posts from Twitter. ++ Import command to preserve those embarssassing old posts from Twitter.
@ -36,7 +68,7 @@ changelog
+ "Bug" fixes. + "Bug" fixes.
-- 0.8.1 === 0.8.1
++ Make it easier to upgrade by decoupling data dir from ".". ++ Make it easier to upgrade by decoupling data dir from ".".
@ -48,7 +80,7 @@ changelog
Syntax highlighting for code blocks. Syntax highlighting for code blocks.
Something resembling an actual manual. Something resembling an actual manual.
-- 0.8.0 === 0.8.0 Ordinary Octology
+++ Add Honk Filtering and Censorship System (HFCS). +++ Add Honk Filtering and Censorship System (HFCS).
@ -103,7 +135,7 @@ changelog
- Sometimes the cached state of the @me feed becomes unsynced. - Sometimes the cached state of the @me feed becomes unsynced.
Acked status may display incorrectly. Acked status may display incorrectly.
-- 0.7.7 === 0.7.7 More 7 Than Ever
+ Add another retry to workaround pixelfed's general unreliability. + Add another retry to workaround pixelfed's general unreliability.
@ -115,11 +147,11 @@ changelog
+ Increase max thread retrieval depth to 10. + Increase max thread retrieval depth to 10.
-- 0.7.6 === 0.7.6
+ Fix a bug where upgrades would not complete in one step. + 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. + Fix a bug (introdcued 0.7.4) preventing new user creation from working.
@ -131,7 +163,7 @@ changelog
+ What may be considered UI improvements. + What may be considered UI improvements.
-- 0.7.4 === 0.7.4
+ Ever more bug fixes. + Ever more bug fixes.
@ -147,19 +179,19 @@ changelog
+ webp image transcoding. + webp image transcoding.
-- 0.7.3 === 0.7.3
+ Better fedicompat so bonks are visible to pleroma followers. + Better fedicompat so bonks are visible to pleroma followers.
-- 0.7.2 === 0.7.2
+ Add the funzone. Minor other UI tweaks. + Add the funzone. Minor other UI tweaks.
-- 0.7.1 === 0.7.1
+ Fix bug preventing unfollow from working. + 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. +++ Auto fetching and inlining of hoots.
@ -189,6 +221,12 @@ changelog
+ Add max-width for video tag. + 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

View File

@ -28,6 +28,12 @@ It is accessed via the
.Pa filters .Pa filters
menu item. menu item.
.Pp .Pp
Each filter has an optional
.Ar name
and
.Ar notes
for user defined purposes.
.Pp
The following match types are possible. The following match types are possible.
All nonempty criteria must match. All nonempty criteria must match.
.Bl -tag -width include-audience .Bl -tag -width include-audience

View File

@ -43,15 +43,18 @@ The
field is required. field is required.
Either of two forms are accepted, the user's handle (or webfinger) or their Either of two forms are accepted, the user's handle (or webfinger) or their
ActivityPub actor URL. ActivityPub actor URL.
The .Pp
.Ar name
field is optional and will be automatically inferred.
Examples:
.Dl @user@example.social .Dl @user@example.social
.Dl https://example.social/users/user .Dl https://example.social/users/user
.Pp .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 Fellow honkers may be added to one or more
.Ic combos .Ar combos
to suit one's organizational preferences. to suit one's organizational preferences.
These are accessed via the These are accessed via the
.Pa combos .Pa combos
@ -143,11 +146,14 @@ The following keywords are supported:
Substring match on the post domain name. Substring match on the post domain name.
.It honker .It honker
Exact match, either AP actor or honker nickname. Exact match, either AP actor or honker nickname.
.It -
Negate term.
.El .El
.Pp .Pp
Example: Example:
.Dl honker:goose big moose .Dl honker:goose big moose -footloose
This query will find honks by the goose about the big moose. This query will find honks by the goose about the big moose, but excluding
those about footloose.
.Ss Filtering .Ss Filtering
Sometimes other users of the federation can get unruly. Sometimes other users of the federation can get unruly.
The honk filtering and censorship system, 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. posts or actor URLs, in which case their recent outbox is imported.
.Ss Account .Ss Account
It's all about you. 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 .Pp
Some options to customize the site appearance: Some options to customize the site appearance:
.Bl -tag -width skinny .Bl -tag -width skinny

View File

@ -109,6 +109,20 @@ If there are no results, wait this many seconds for something to appear.
.El .El
.Pp .Pp
The result will be returned as json. 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 .Sh EXAMPLES
Refer to the sample code in the Refer to the sample code in the
.Pa toys .Pa toys

View File

@ -100,6 +100,8 @@ Displayed on the home page.
Displayed on the about page. Displayed on the about page.
.It login .It login
Displayed about the login form. Displayed about the login form.
.It avatar colors
Four 32-bit hex colors (RGBA).
.El .El
.Pp .Pp
.Ss User Admin .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 The current version of the honk binary may be printed with the
.Ic version .Ic version
command. 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 .Ss Security
.Nm .Nm
is not currently hardened against SSRF, server side request forgery. 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 . .Ic debug Ar on|off .
In debug mode, secure cookies are disabled and templates are reloaded In debug mode, secure cookies are disabled and templates are reloaded
every request. every request.
Debug mode is really more useful for development, not debugging production.
.Ss Import .Ss Import
Data may be imported and converted from other services using the Data may be imported and converted from other services using the
.Ic import .Ic import
@ -197,6 +206,12 @@ honk-v98> ./honk -datadir ../honkdata admin
honk-v98> date; ./honk -datadir ../honkdata >> log 2>&1 honk-v98> date; ./honk -datadir ../honkdata >> log 2>&1
.Ed .Ed
.Pp .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. Upgrade to the next version.
Clean things up a bit. Clean things up a bit.
.Bd -literal -offset indent .Bd -literal -offset indent

View File

@ -274,15 +274,6 @@ a.In { }
font-weight: normal; font-weight: normal;
font-family: monospace; } 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. */ /* Overrides to avoid excessive margins on small devices. */
@media (max-width: 37.5em) { @media (max-width: 37.5em) {

126
fun.go
View File

@ -28,6 +28,7 @@ import (
"os" "os"
"regexp" "regexp"
"strings" "strings"
"time"
"golang.org/x/net/html" "golang.org/x/net/html"
"humungus.tedunangst.com/r/webs/cache" "humungus.tedunangst.com/r/webs/cache"
@ -60,17 +61,18 @@ func reverbolate(userid int64, honks []*Honk) {
if !h.Public { if !h.Public {
h.Style += " limited" h.Style += " limited"
} }
translate(h, false) translate(h)
if h.Whofore == 2 || h.Whofore == 3 { local := false
h.URL = h.XID if (h.Whofore == 2 || h.Whofore == 3) && h.What != "bonked" {
if h.What != "bonked" { local = true
h.Noise = re_memes.ReplaceAllString(h.Noise, "") }
h.Noise = mentionize(h.Noise) if local {
h.Noise = ontologize(h.Noise) h.Noise = re_memes.ReplaceAllString(h.Noise, "")
} h.Noise = mentionize(h.Noise)
h.Username, h.Handle = handles(h.Honker) h.Noise = ontologize(h.Noise)
} else { }
_, h.Handle = handles(h.Honker) h.Username, h.Handle = handles(h.Honker)
if !local {
short := shortname(userid, h.Honker) short := shortname(userid, h.Honker)
if short != "" { if short != "" {
h.Username = short h.Username = short
@ -80,9 +82,9 @@ func reverbolate(userid int64, honks []*Honk) {
h.Username = h.Username[:20] + ".." h.Username = h.Username[:20] + ".."
} }
} }
if h.URL == "" { }
h.URL = h.XID if h.URL == "" {
} h.URL = h.XID
} }
if h.Oonker != "" { if h.Oonker != "" {
_, h.Oondle = handles(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 return e
} }
h.Precis = re_emus.ReplaceAllStringFunc(h.Precis, emuxifier) h.Precis = re_emus.ReplaceAllStringFunc(h.Precis, emuxifier)
@ -189,7 +198,7 @@ func imaginate(honk *Honk) {
htf.String(honk.Noise) htf.String(honk.Noise)
} }
func translate(honk *Honk, redoimages bool) { func translate(honk *Honk) {
if honk.Format == "html" { if honk.Format == "html" {
return return
} }
@ -210,31 +219,31 @@ func translate(honk *Honk, redoimages bool) {
noise = markitzero(noise) noise = markitzero(noise)
honk.Noise = noise honk.Noise = noise
honk.Onts = oneofakind(ontologies(honk.Noise)) honk.Onts = oneofakind(ontologies(honk.Noise))
}
if redoimages { func redoimages(honk *Honk) {
zap := make(map[string]bool) zap := make(map[string]bool)
{ {
var htf htfilter.Filter var htf htfilter.Filter
htf.Imager = replaceimgsand(zap, true) htf.Imager = replaceimgsand(zap, true)
htf.SpanClasses = allowedclasses htf.SpanClasses = allowedclasses
p, _ := htf.String(honk.Precis) p, _ := htf.String(honk.Precis)
n, _ := htf.String(honk.Noise) n, _ := htf.String(honk.Noise)
honk.Precis = string(p) honk.Precis = string(p)
honk.Noise = string(n) 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)
} }
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)
} }
func xcelerate(b []byte) string { func xcelerate(b []byte) string {
@ -276,11 +285,6 @@ func ontologies(s string) []string {
return m[:j] return m[:j]
} }
type Mention struct {
who string
where string
}
var re_mentions = regexp.MustCompile(`@[[:alnum:]._-]+@[[:alnum:].-]*[[:alnum:]]`) var re_mentions = regexp.MustCompile(`@[[:alnum:]._-]+@[[:alnum:].-]*[[:alnum:]]`)
var re_urltions = regexp.MustCompile(`@https://\S+`) var re_urltions = regexp.MustCompile(`@https://\S+`)
@ -306,12 +310,12 @@ func bunchofgrapes(s string) []Mention {
for i := range m { for i := range m {
where := gofish(m[i]) where := gofish(m[i])
if where != "" { 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) m = re_urltions.FindAllString(s, -1)
for i := range m { 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 return mentions
} }
@ -323,23 +327,33 @@ type Emu struct {
var re_emus = regexp.MustCompile(`:[[:alnum:]_-]+:`) 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 { func herdofemus(noise string) []Emu {
m := re_emus.FindAllString(noise, -1) m := re_emus.FindAllString(noise, -1)
m = oneofakind(m) m = oneofakind(m)
var emus []Emu var emus []Emu
for _, e := range m { for _, e := range m {
fname := e[1 : len(e)-1] var emu Emu
_, err := os.Stat("emus/" + fname + ".png") emucache.Get(e, &emu)
if err != nil { if emu.ID == "" {
continue continue
} }
url := fmt.Sprintf("https://%s/emu/%s.png", serverName, fname) emus = append(emus, emu)
emus = append(emus, Emu{ID: url, Name: e})
} }
return emus 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) { func memetize(honk *Honk) {
repl := func(x string) string { repl := func(x string) string {
@ -377,7 +391,7 @@ func memetize(honk *Honk) {
honk.Noise = re_memes.ReplaceAllStringFunc(honk.Noise, repl) 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 { func quickrename(s string, userid int64) string {
nonstop := true nonstop := true
@ -392,7 +406,7 @@ func quickrename(s string, userid int64) string {
prefix += "@" prefix += "@"
m = m[1:] m = m[1:]
tail := "" 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:] tail = m[len(m)-1:]
m = m[:len(m)-1] m = m[:len(m)-1]
} }
@ -613,6 +627,12 @@ func zaggy(keyname string) *rsa.PublicKey {
return key return key
} }
func savingthrow(keyname string) {
when := time.Now().UTC().Add(-30 * time.Minute).Format(dbtimeformat)
stmtDeleteXonker.Exec(keyname, "pubkey", when)
zaggies.Clear(keyname)
}
func keymatch(keyname string, actor string) string { func keymatch(keyname string, actor string) string {
hash := strings.IndexByte(keyname, '#') hash := strings.IndexByte(keyname, '#')
if hash == -1 { if hash == -1 {

52
hfcs.go
View File

@ -44,6 +44,7 @@ type Filter struct {
re_rewrite *regexp.Regexp re_rewrite *regexp.Regexp
Replace string `json:",omitempty"` Replace string `json:",omitempty"`
Expiration time.Time Expiration time.Time
Notes string
} }
type filtType uint type filtType uint
@ -168,15 +169,24 @@ func getfilters(userid int64, scope filtType) []*Filter {
return nil return nil
} }
func rejectorigin(userid int64, origin string) bool { func rejectorigin(userid int64, origin string, isannounce bool) bool {
if o := originate(origin); o != "" { if o := originate(origin); o != "" {
origin = o origin = o
} }
filts := getfilters(userid, filtReject) filts := getfilters(userid, filtReject)
for _, f := range filts { for _, f := range filts {
if f.IsAnnounce || f.Text != "" { if f.Text != "" {
continue continue
} }
if f.IsAnnounce {
if !isannounce {
continue
}
if f.AnnounceOf == origin {
log.Printf("rejecting announce: %s", origin)
return true
}
}
if f.Actor == origin { if f.Actor == origin {
log.Printf("rejecting origin: %s", origin) log.Printf("rejecting origin: %s", origin)
return true return true
@ -204,7 +214,7 @@ func stealthmode(userid int64, r *http.Request) bool {
agent := r.UserAgent() agent := r.UserAgent()
agent = originate(agent) agent = originate(agent)
if agent != "" { if agent != "" {
fake := rejectorigin(userid, agent) fake := rejectorigin(userid, agent, false)
if fake { if fake {
log.Printf("faking 404 for %s", agent) log.Printf("faking 404 for %s", agent)
return true return true
@ -214,21 +224,29 @@ func stealthmode(userid int64, r *http.Request) bool {
} }
func matchfilter(h *Honk, f *Filter) bool { func matchfilter(h *Honk, f *Filter) bool {
return matchfilterX(h, f) != ""
}
func matchfilterX(h *Honk, f *Filter) string {
rv := ""
match := true match := true
if match && f.Actor != "" { if match && f.Actor != "" {
match = false match = false
if f.Actor == h.Honker || f.Actor == h.Oonker { if f.Actor == h.Honker || f.Actor == h.Oonker {
match = true match = true
rv = f.Actor
} }
if !match && (f.Actor == originate(h.Honker) || if !match && (f.Actor == originate(h.Honker) ||
f.Actor == originate(h.Oonker) || f.Actor == originate(h.Oonker) ||
f.Actor == originate(h.XID)) { f.Actor == originate(h.XID)) {
match = true match = true
rv = f.Actor
} }
if !match && f.IncludeAudience { if !match && f.IncludeAudience {
for _, a := range h.Audience { for _, a := range h.Audience {
if f.Actor == a || f.Actor == originate(a) { if f.Actor == a || f.Actor == originate(a) {
match = true match = true
rv = f.Actor
break break
} }
} }
@ -239,23 +257,33 @@ func matchfilter(h *Honk, f *Filter) bool {
if (f.AnnounceOf == "" && h.Oonker != "") || f.AnnounceOf == h.Oonker || if (f.AnnounceOf == "" && h.Oonker != "") || f.AnnounceOf == h.Oonker ||
f.AnnounceOf == originate(h.Oonker) { f.AnnounceOf == originate(h.Oonker) {
match = true match = true
rv += " announce"
} }
} }
if match && f.Text != "" { if match && f.Text != "" {
match = false match = false
re := f.re_text re := f.re_text
if re.MatchString(h.Noise) || re.MatchString(h.Precis) { m := re.FindString(h.Precis)
match = true if m == "" {
m = re.FindString(h.Noise)
} }
if !match { if m == "" {
for _, d := range h.Donks { for _, d := range h.Donks {
if re.MatchString(d.Desc) { m = re.FindString(d.Desc)
match = true if m != "" {
break
} }
} }
} }
if m != "" {
match = true
rv = m
}
} }
return match if match {
return rv
}
return ""
} }
func rejectxonk(xonk *Honk) bool { func rejectxonk(xonk *Honk) bool {
@ -282,11 +310,7 @@ func skipMedia(xonk *Honk) bool {
func unsee(userid int64, h *Honk) { func unsee(userid int64, h *Honk) {
filts := getfilters(userid, filtCollapse) filts := getfilters(userid, filtCollapse)
for _, f := range filts { for _, f := range filts {
if matchfilter(h, f) { if bad := matchfilterX(h, f); bad != "" {
bad := f.Text
if f.Actor != "" {
bad = f.Actor
}
if h.Precis == "" { if h.Precis == "" {
h.Precis = bad h.Precis = bad
} }

29
honk.go
View File

@ -88,6 +88,12 @@ type Honk struct {
Onts []string Onts []string
Place *Place Place *Place
Time *Time Time *Time
Mentions []Mention
}
type Mention struct {
Who string
Where string
} }
type OldRevision struct { type OldRevision struct {
@ -173,6 +179,11 @@ type Honker struct {
Handle string Handle string
Flavor string Flavor string
Combos []string Combos []string
Meta HonkerMeta
}
type HonkerMeta struct {
Notes string
} }
type SomeThing struct { type SomeThing struct {
@ -199,6 +210,13 @@ var loginMsg template.HTML
func ElaborateUnitTests() { 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() { func main() {
flag.StringVar(&dataDir, "datadir", dataDir, "data directory") flag.StringVar(&dataDir, "datadir", dataDir, "data directory")
flag.StringVar(&viewDir, "viewdir", viewDir, "view directory") flag.StringVar(&viewDir, "viewdir", viewDir, "view directory")
@ -244,9 +262,9 @@ func main() {
} }
switch args[1] { switch args[1] {
case "on": case "on":
updateconfig("debug", 1) setconfig("debug", 1)
case "off": case "off":
updateconfig("debug", 0) setconfig("debug", 0)
default: default:
log.Fatal("argument must be on or off") log.Fatal("argument must be on or off")
} }
@ -266,6 +284,13 @@ func main() {
arg = args[1] arg = args[1]
} }
cleanupdb(arg) cleanupdb(arg)
case "unplug":
if len(args) < 2 {
fmt.Printf("usage: honk unplug servername\n")
return
}
name := args[1]
unplugserver(name)
case "ping": case "ping":
if len(args) < 3 { if len(args) < 3 {
fmt.Printf("usage: honk ping from to\n") fmt.Printf("usage: honk ping from to\n")

View File

@ -28,7 +28,8 @@ var re_bolder = regexp.MustCompile(`(^|\W)\*\*((?s:.*?))\*\*($|\W)`)
var re_italicer = regexp.MustCompile(`(^|\W)\*((?s:.*?))\*($|\W)`) var re_italicer = regexp.MustCompile(`(^|\W)\*((?s:.*?))\*($|\W)`)
var re_bigcoder = regexp.MustCompile("```(.*)\n?((?s:.*?))\n?```\n?") var re_bigcoder = regexp.MustCompile("```(.*)\n?((?s:.*?))\n?```\n?")
var re_coder = regexp.MustCompile("`([^`]*)`") 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_link = regexp.MustCompile(`.?.?https?://[^\s"]+[\w/)!]`)
var re_zerolink = regexp.MustCompile(`\[([^]]*)\]\(([^)]*\)?)\)`) var re_zerolink = regexp.MustCompile(`\[([^]]*)\]\(([^)]*\)?)\)`)
var re_imgfix = regexp.MustCompile(`<img ([^>]*)>`) 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_zerolink.ReplaceAllString(s, `<a href="$2">$1</a>`)
s = re_bolder.ReplaceAllString(s, "$1<b>$2</b>$3") s = re_bolder.ReplaceAllString(s, "$1<b>$2</b>$3")
s = re_italicer.ReplaceAllString(s, "$1<i>$2</i>$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 = strings.Replace(s, "\n---\n", "<hr><p>", -1)
s = re_lister.ReplaceAllStringFunc(s, func(m string) string { s = re_lister.ReplaceAllStringFunc(s, func(m string) string {
@ -117,6 +119,7 @@ func markitzero(s string) string {
// some final fixups // some final fixups
s = strings.Replace(s, "\n", "<br>", -1) s = strings.Replace(s, "\n", "<br>", -1)
s = strings.Replace(s, "<br><blockquote>", "<blockquote>", -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><pre>", "<pre>", -1)
s = strings.Replace(s, "<br><ul>", "<ul>", -1) s = strings.Replace(s, "<br><ul>", "<ul>", -1)
s = strings.Replace(s, "<p><br>", "<p>", -1) s = strings.Replace(s, "<p><br>", "<p>", -1)

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"strings"
"testing" "testing"
) )
@ -106,3 +107,22 @@ para
output := `hello<ul><li>a list<li>the list</ul><p>para<ul><li>singleton</ul><p>` output := `hello<ul><li>a list<li>the list</ul><p>para<ul><li>singleton</ul><p>`
doonezerotest(t, input, output) 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)
}
}

View File

@ -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 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 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 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 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); 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 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 doovers(dooverid integer primary key, dt text, tries integer, userid integer, rcpt text, msg blob);
create table onts (ontology text, honkid integer); create table onts (ontology text, honkid integer);

View File

@ -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 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 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 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 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); 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 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 doovers(dooverid integer primary key, dt text, tries integer, userid integer, rcpt text, msg blob);
create table onts (ontology text, honkid integer); create table onts (ontology text, honkid integer);

View File

@ -21,87 +21,9 @@ import (
"github.com/mattn/go-runewidth" "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 skinTones = "\U0001F3FB\U0001F3FC\U0001F3FD\U0001F3FE\U0001F3FF"
var re_moredumb = regexp.MustCompile("[\U0001f44f\U0001f6a8\U000026a0][" + skinTones + "\ufe0f]*") 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 { func demoji(s string) string {
s = re_moredumb.ReplaceAllString(s, ".") s = re_moredumb.ReplaceAllString(s, ".")

View File

@ -1,5 +1,5 @@
all: gettoken saytheday youvegothonks all: gettoken saytheday sprayandpray youvegothonks
gettoken: gettoken.go gettoken: gettoken.go
go build gettoken.go go build gettoken.go
@ -7,5 +7,8 @@ gettoken: gettoken.go
saytheday: saytheday.go saytheday: saytheday.go
go build saytheday.go go build saytheday.go
sprayandpray: sprayandpray.go
go build sprayandpray.go
youvegothonks: youvegothonks.go youvegothonks: youvegothonks.go
go build youvegothonks.go go build youvegothonks.go

View File

@ -4,6 +4,8 @@ A little of this, a little of that.
gettoken.go - obtains an authorization token 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 youvegothonks.go - polls for new mesages

59
toys/sprayandpray.go Normal file
View File

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

View File

@ -62,4 +62,7 @@ func init() {
C.unveil(nil, nil) C.unveil(nil, nil)
Pledge("stdio rpath wpath cpath flock dns inet unix") Pledge("stdio rpath wpath cpath flock dns inet unix")
}) })
backendhooks = append(backendhooks, func() {
Pledge("stdio unix")
})
} }

View File

@ -24,7 +24,7 @@ import (
"time" "time"
) )
var myVersion = 32 var myVersion = 34
func doordie(db *sql.DB, s string, args ...interface{}) { func doordie(db *sql.DB, s string, args ...interface{}) {
_, err := db.Exec(s, args...) _, err := db.Exec(s, args...)
@ -356,6 +356,16 @@ func upgradedb() {
doordie(db, "update config set value = 32 where key = 'dbversion'") doordie(db, "update config set value = 32 where key = 'dbversion'")
fallthrough fallthrough
case 32: 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: default:
log.Fatalf("can't upgrade unknown version %d", dbversion) log.Fatalf("can't upgrade unknown version %d", dbversion)

View File

@ -412,16 +412,11 @@ func getconfig(key string, value interface{}) error {
func setconfig(key string, val interface{}) error { func setconfig(key string, val interface{}) error {
db := opendatabase() db := opendatabase()
db.Exec("delete from config where key = ?", key)
_, err := db.Exec("insert into config (key, value) values (?, ?)", key, val) _, err := db.Exec("insert into config (key, value) values (?, ?)", key, val)
return err 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) { func openListener() (net.Listener, error) {
var listenAddr string var listenAddr string
err := getconfig("listenaddr", &listenAddr) err := getconfig("listenaddr", &listenAddr)

View File

@ -7,7 +7,7 @@
<form id="aboutform" action="/saveuser" method="POST"> <form id="aboutform" action="/saveuser" method="POST">
<input type="hidden" name="CSRF" value="{{ .UserCSRF }}"> <input type="hidden" name="CSRF" value="{{ .UserCSRF }}">
<p>about me: <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> <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> <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> <p><label class="button" for="maps">apple map links:</label>

View File

@ -9,6 +9,9 @@ Honk Filtering and Censorship System
<h3>new filter</h3> <h3>new filter</h3>
<p><label for="name">filter name:</label><br> <p><label for="name">filter name:</label><br>
<input tabindex=1 type="text" name="name" value="" autocomplete=off> <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> <hr>
<h3>match</h3> <h3>match</h3>
<p><label for="actor">who or where:</label><br> <p><label for="actor">who or where:</label><br>
@ -48,6 +51,7 @@ Honk Filtering and Censorship System
{{ range .Filters }} {{ range .Filters }}
<section class="honk"> <section class="honk">
<p>Name: {{ .Name }} <p>Name: {{ .Name }}
{{ with .Notes }}<p>Notes: {{ . }}{{ end }}
<p>Date: {{ .Date.Format "2006-01-02" }} <p>Date: {{ .Date.Format "2006-01-02" }}
{{ with .Actor }}<p>Who: {{ . }}{{ end }} {{ with .IncludeAudience }} (inclusive) {{ end }} {{ with .Actor }}<p>Who: {{ . }}{{ end }} {{ with .IncludeAudience }} (inclusive) {{ end }}
{{ with .Text }}<p>Text: {{ . }}{{ end }} {{ with .Text }}<p>Text: {{ . }}{{ end }}

View File

@ -66,15 +66,15 @@ in reply to: <a href="{{ .RID }}" rel=noreferrer>{{ .RID }}</a>
{{ range .Donks }} {{ range .Donks }}
{{ if .Local }} {{ if .Local }}
{{ if eq .Media "text/plain" }} {{ 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" }} {{ 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 }} {{ else }}
<p><img src="/d/{{ .XID }}" title="{{ .Desc }}" alt="{{ .Desc }}"> <p><img src="/d/{{ .XID }}" title="{{ .Desc }}" alt="{{ .Desc }}">
{{ end }} {{ end }}
{{ else }} {{ else }}
{{ if .XID }} {{ 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 }} {{ else }}
{{ if eq .Media "video/mp4" }} {{ if eq .Media "video/mp4" }}
<p><video controls src="{{ .URL }}">{{ .Name }}</video> <p><video controls src="{{ .URL }}">{{ .Name }}</video>

View File

@ -13,6 +13,9 @@
<input tabindex=1 type="text" name="combos" value="" placeholder="optional"> <input tabindex=1 type="text" name="combos" value="" placeholder="optional">
<p><span><label class=button for="peep">skip subscribe: <p><span><label class=button for="peep">skip subscribe:
<input tabindex=1 type="checkbox" id="peep" name="peep" value="peep"><span></span></label></span> <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> <p><button tabindex=1 name="add honker" value="add honker">add honker</button>
</form> </form>
</div> </div>
@ -42,6 +45,8 @@ function expandstuff() {
<input type="hidden" name="CSRF" value="{{ $honkercsrf }}"> <input type="hidden" name="CSRF" value="{{ $honkercsrf }}">
<input type="hidden" name="honkerid" value="{{ .ID }}"> <input type="hidden" name="honkerid" value="{{ .ID }}">
<p>name: <input type="text" name="name" value="{{ .Name }}"> <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}}"> <p>combos: <input type="text" name="combos" value="{{ range .Combos }}{{ . }} {{end}}">
{{ if eq .Flavor "sub" }} {{ if eq .Flavor "sub" }}
<p>unsub: <input type="text" name="goodbye" placeholder="press F" value="" autocomplete=off> <p>unsub: <input type="text" name="goodbye" placeholder="press F" value="" autocomplete=off>

7
views/pleroma.css Normal file
View File

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

View File

@ -1,8 +1,9 @@
html { html {
--bg-page: #305; --bg-page: #306;
--bg-dark: #002; --bg-dark: #002;
--fg: #dde; --fg: #dcf;
--fg-subtle: #aab; --hl: #dcf;
--fg-subtle: #a9c;
--fg-limited: #a79; --fg-limited: #a79;
} }
@ -23,6 +24,9 @@ blockquote {
padding-left: 0.5em; padding-left: 0.5em;
border-left: 1px solid var(--fg-subtle); border-left: 1px solid var(--fg-subtle);
} }
blockquote cite {
margin-left: 2em;
}
table { table {
display: block; display: block;
max-width: 100%; max-width: 100%;
@ -73,7 +77,7 @@ header > details {
header > details[open] { header > details[open] {
padding: 1em 1em 0em 1em; padding: 1em 1em 0em 1em;
background: var(--bg-dark); background: var(--bg-dark);
border: 1px solid var(--fg); border: 1px solid var(--hl);
margin-bottom: 1em; margin-bottom: 1em;
opacity: 1.0; opacity: 1.0;
} }
@ -99,9 +103,12 @@ main {
margin: auto; margin: auto;
font-size: 1.5em; font-size: 1.5em;
} }
hr {
border-color: var(--hl);
}
.info { .info {
background: var(--bg-dark); background: var(--bg-dark);
border: 1px solid var(--fg); border: 1px solid var(--hl);
margin-bottom: 1em; margin-bottom: 1em;
padding: 0em 1em 0em 1em; padding: 0em 1em 0em 1em;
} }
@ -117,7 +124,7 @@ label.button, button, select {
font-family: monospace; font-family: monospace;
color: var(--fg); color: var(--fg);
background: var(--bg-page); background: var(--bg-page);
border: 1px solid var(--fg); border: 1px solid var(--hl);
padding: 0.5em; padding: 0.5em;
white-space: nowrap; white-space: nowrap;
} }
@ -140,11 +147,14 @@ textarea {
background: var(--bg-page); background: var(--bg-page);
color: var(--fg); color: var(--fg);
width: 600px; width: 600px;
height: 8em; height: 4em;
margin-bottom: 0.5em; margin-bottom: 0.5em;
box-sizing: border-box; box-sizing: border-box;
max-width: 100%; max-width: 100%;
} }
textarea#honknoise {
height: 10em;
}
input[type="checkbox"] { input[type="checkbox"] {
position: fixed; position: fixed;
top: -9999px; top: -9999px;
@ -163,13 +173,13 @@ input[type=file] {
} }
.glow { .glow {
box-shadow: 0px 0px 16px var(--fg); box-shadow: 0px 0px 16px var(--hl);
} }
.honk { .honk {
margin: auto; margin: auto;
background: var(--bg-dark); background: var(--bg-dark);
border: 1px solid var(--fg); border: 1px solid var(--hl);
border-radius: 1em; border-radius: 1em;
margin-bottom: 1em; margin-bottom: 1em;
padding-left: 1em; padding-left: 1em;

142
web.go
View File

@ -48,6 +48,8 @@ var readviews *templates.Template
var userSep = "u" var userSep = "u"
var honkSep = "h" var honkSep = "h"
var debugMode = false
func getuserstyle(u *login.UserInfo) template.CSS { func getuserstyle(u *login.UserInfo) template.CSS {
if u == nil { if u == nil {
return "" return ""
@ -223,8 +225,10 @@ func showrss(w http.ResponseWriter, r *http.Request) {
modtime = honk.Date modtime = honk.Date
} }
} }
w.Header().Set("Cache-Control", "max-age=300") if !debugMode {
w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat)) w.Header().Set("Cache-Control", "max-age=300")
w.Header().Set("Last-Modified", modtime.Format(http.TimeFormat))
}
err := feed.Write(w) err := feed.Write(w)
if err != nil { if err != nil {
@ -330,6 +334,10 @@ func inbox(w http.ResponseWriter, r *http.Request) {
} }
keyname, err := httpsig.VerifyRequest(r, payload, zaggy) keyname, err := httpsig.VerifyRequest(r, payload, zaggy)
if err != nil && keyname != "" {
savingthrow(keyname)
keyname, err = httpsig.VerifyRequest(r, payload, zaggy)
}
if err != nil { if err != nil {
log.Printf("inbox message failed signature for %s from %s", keyname, r.Header.Get("X-Forwarded-For")) log.Printf("inbox message failed signature for %s from %s", keyname, r.Header.Get("X-Forwarded-For"))
if keyname != "" { if keyname != "" {
@ -460,6 +468,10 @@ func serverinbox(w http.ResponseWriter, r *http.Request) {
return return
} }
keyname, err := httpsig.VerifyRequest(r, payload, zaggy) keyname, err := httpsig.VerifyRequest(r, payload, zaggy)
if err != nil && keyname != "" {
savingthrow(keyname)
keyname, err = httpsig.VerifyRequest(r, payload, zaggy)
}
if err != nil { if err != nil {
log.Printf("inbox message failed signature for %s from %s", keyname, r.Header.Get("X-Forwarded-For")) log.Printf("inbox message failed signature for %s from %s", keyname, r.Header.Get("X-Forwarded-For"))
if keyname != "" { if keyname != "" {
@ -866,7 +878,7 @@ func thelistingoftheontologies(w http.ResponseWriter, r *http.Request) {
sort.Slice(onts, func(i, j int) bool { sort.Slice(onts, func(i, j int) bool {
return onts[i].Name < onts[j].Name return onts[i].Name < onts[j].Name
}) })
if u == nil { if u == nil && !debugMode {
w.Header().Set("Cache-Control", "max-age=300") w.Header().Set("Cache-Control", "max-age=300")
} }
templinfo := getInfo(r) templinfo := getInfo(r)
@ -882,6 +894,33 @@ type Track struct {
who string who string
} }
func getbacktracks(xid string) []string {
c := make(chan bool)
dumptracks <- c
<-c
row := stmtGetTracks.QueryRow(xid)
var rawtracks string
err := row.Scan(&rawtracks)
if err != nil {
if err != sql.ErrNoRows {
log.Printf("error scanning tracks: %s", err)
}
return nil
}
var rcpts []string
for _, f := range strings.Split(rawtracks, " ") {
idx := strings.LastIndexByte(f, '#')
if idx != -1 {
f = f[:idx]
}
if !strings.HasPrefix(f, "https://") {
f = fmt.Sprintf("%%https://%s/inbox", f)
}
rcpts = append(rcpts, f)
}
return rcpts
}
func savetracks(tracks map[string][]string) { func savetracks(tracks map[string][]string) {
db := opendatabase() db := opendatabase()
tx, err := db.Begin() tx, err := db.Begin()
@ -932,6 +971,7 @@ func savetracks(tracks map[string][]string) {
} }
var trackchan = make(chan Track) var trackchan = make(chan Track)
var dumptracks = make(chan chan bool)
func tracker() { func tracker() {
timeout := 4 * time.Minute timeout := 4 * time.Minute
@ -947,6 +987,11 @@ func tracker() {
tracks = make(map[string][]string) tracks = make(map[string][]string)
} }
sleeper.Reset(timeout) sleeper.Reset(timeout)
case c := <-dumptracks:
if len(tracks) > 0 {
savetracks(tracks)
}
c <- true
case <-endoftheworld: case <-endoftheworld:
if len(tracks) > 0 { if len(tracks) > 0 {
savetracks(tracks) savetracks(tracks)
@ -1052,7 +1097,7 @@ func honkpage(w http.ResponseWriter, u *login.UserInfo, honks []*Honk, templinfo
templinfo["TopHID"] = 0 templinfo["TopHID"] = 0
} }
} }
if u == nil { if u == nil && !debugMode {
w.Header().Set("Cache-Control", "max-age=60") w.Header().Set("Cache-Control", "max-age=60")
} }
err := readviews.Execute(w, "honkpage.html", templinfo) err := readviews.Execute(w, "honkpage.html", templinfo)
@ -1077,6 +1122,17 @@ func saveuser(w http.ResponseWriter, r *http.Request) {
} else { } else {
options.MapLink = "" 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) j, err := jsonify(options)
if err == nil { if err == nil {
_, err = db.Exec("update users set about = ?, options = ? where username = ?", whatabout, j, u.Username) _, 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 = strings.Replace(noise, "\r", "", -1)
noise = quickrename(noise, userinfo.UserID) noise = quickrename(noise, userinfo.UserID)
noise = hooterize(noise) noise = hooterize(noise)
honk.Mentions = bunchofgrapes(noise)
honk.Noise = noise honk.Noise = noise
translate(honk, false) translate(honk)
var convoy string var convoy string
if rid != "" { if rid != "" {
@ -1406,6 +1463,12 @@ func submithonk(w http.ResponseWriter, r *http.Request, isAPI bool) {
} }
} }
honk.RID = rid 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 { } else {
honk.Audience = []string{thewholeworld} 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) 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) memetize(honk)
imaginate(honk) imaginate(honk)
@ -1664,6 +1719,10 @@ func submithonker(w http.ResponseWriter, r *http.Request) {
combos = " " + combos + " " combos = " " + combos + " "
honkerid, _ := strconv.ParseInt(r.FormValue("honkerid"), 10, 0) 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) defer honkerinvalidator.Clear(u.UserID)
if honkerid > 0 { if honkerid > 0 {
@ -1714,7 +1773,7 @@ func submithonker(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/honkers", http.StatusSeeOther) http.Redirect(w, r, "/honkers", http.StatusSeeOther)
return return
} }
_, err := stmtUpdateHonker.Exec(name, combos, honkerid, u.UserID) _, err := stmtUpdateHonker.Exec(name, combos, mj, honkerid, u.UserID)
if err != nil { if err != nil {
log.Printf("update honker err: %s", err) log.Printf("update honker err: %s", err)
return return
@ -1738,7 +1797,7 @@ func submithonker(w http.ResponseWriter, r *http.Request) {
if name == "" { if name == "" {
name = url 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 { if err != nil {
log.Print(err) log.Print(err)
return return
@ -1770,7 +1829,7 @@ func submithonker(w http.ResponseWriter, r *http.Request) {
if name == "" { if name == "" {
name = info.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 { if err != nil {
log.Print(err) log.Print(err)
return return
@ -1827,6 +1886,7 @@ func savehfcs(w http.ResponseWriter, r *http.Request) {
if dur := parseDuration(r.FormValue("filtduration")); dur > 0 { if dur := parseDuration(r.FormValue("filtduration")); dur > 0 {
filt.Expiration = time.Now().UTC().Add(dur) filt.Expiration = time.Now().UTC().Add(dur)
} }
filt.Notes = strings.TrimSpace(r.FormValue("filtnotes"))
if filt.Actor == "" && filt.Text == "" && !filt.IsAnnounce { if filt.Actor == "" && filt.Text == "" && !filt.IsAnnounce {
log.Printf("blank filter") log.Printf("blank filter")
@ -1853,6 +1913,11 @@ func accountpage(w http.ResponseWriter, r *http.Request) {
templinfo["UserCSRF"] = login.GetCSRF("saveuser", r) templinfo["UserCSRF"] = login.GetCSRF("saveuser", r)
templinfo["LogoutCSRF"] = login.GetCSRF("logout", r) templinfo["LogoutCSRF"] = login.GetCSRF("logout", r)
templinfo["User"] = user 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) err := readviews.Execute(w, "account.html", templinfo)
if err != nil { if err != nil {
log.Print(err) log.Print(err)
@ -1923,14 +1988,19 @@ func somedays() string {
} }
func avatate(w http.ResponseWriter, r *http.Request) { func avatate(w http.ResponseWriter, r *http.Request) {
if debugMode {
loadAvatarColors()
}
n := r.FormValue("a") n := r.FormValue("a")
a := avatar(n) a := genAvatar(n)
w.Header().Set("Cache-Control", "max-age="+somedays()) w.Header().Set("Cache-Control", "max-age="+somedays())
w.Write(a) w.Write(a)
} }
func serveasset(w http.ResponseWriter, r *http.Request) { 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 dir := viewDir
if r.URL.Path == "/local.css" { if r.URL.Path == "/local.css" {
dir = dataDir dir = dataDir
@ -1939,7 +2009,9 @@ func serveasset(w http.ResponseWriter, r *http.Request) {
} }
func servehelp(w http.ResponseWriter, r *http.Request) { func servehelp(w http.ResponseWriter, r *http.Request) {
name := mux.Vars(r)["name"] 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) http.ServeFile(w, r, viewDir+"/docs/"+name)
} }
func servehtml(w http.ResponseWriter, r *http.Request) { 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" { if r.URL.Path == "/about" {
templinfo["Sensors"] = getSensors() templinfo["Sensors"] = getSensors()
} }
if u == nil { if u == nil && !debugMode {
w.Header().Set("Cache-Control", "max-age=60") w.Header().Set("Cache-Control", "max-age=60")
} }
err := readviews.Execute(w, r.URL.Path[1:]+".html", templinfo) 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) { 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()) 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) { 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()) 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) { func servefile(w http.ResponseWriter, r *http.Request) {
@ -2073,6 +2147,7 @@ func honkhonkline() {
func apihandler(w http.ResponseWriter, r *http.Request) { func apihandler(w http.ResponseWriter, r *http.Request) {
u := login.GetUserInfo(r) u := login.GetUserInfo(r)
userid := u.UserID userid := u.UserID
user, _ := butwhatabout(u.Username)
action := r.FormValue("action") action := r.FormValue("action")
wait, _ := strconv.ParseInt(r.FormValue("wait"), 10, 0) wait, _ := strconv.ParseInt(r.FormValue("wait"), 10, 0)
log.Printf("api request '%s' on behalf of %s", action, u.Username) 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 := junk.New()
j["honks"] = honks j["honks"] = honks
j.Write(w) 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: default:
http.Error(w, "unknown action", http.StatusNotFound) http.Error(w, "unknown action", http.StatusNotFound)
return return
@ -2158,9 +2240,8 @@ func serve() {
go redeliverator() go redeliverator()
go tracker() go tracker()
debug := false getconfig("debug", &debugMode)
getconfig("debug", &debug) readviews = templates.Load(debugMode,
readviews = templates.Load(debug,
viewDir+"/views/honkpage.html", viewDir+"/views/honkpage.html",
viewDir+"/views/honkfrags.html", viewDir+"/views/honkfrags.html",
viewDir+"/views/honkers.html", viewDir+"/views/honkers.html",
@ -2178,11 +2259,12 @@ func serve() {
viewDir+"/views/onts.html", viewDir+"/views/onts.html",
viewDir+"/views/honkpage.js", viewDir+"/views/honkpage.js",
) )
if !debug { if !debugMode {
assets := []string{viewDir + "/views/style.css", dataDir + "/views/local.css", viewDir + "/views/honkpage.js"} assets := []string{viewDir + "/views/style.css", dataDir + "/views/local.css", viewDir + "/views/honkpage.js"}
for _, s := range assets { for _, s := range assets {
savedassetparams[s] = getassetparam(s) savedassetparams[s] = getassetparam(s)
} }
loadAvatarColors()
} }
for _, h := range preservehooks { for _, h := range preservehooks {
@ -2214,8 +2296,8 @@ func serve() {
getters.HandleFunc("/o", thelistingoftheontologies) getters.HandleFunc("/o", thelistingoftheontologies)
getters.HandleFunc("/o/{name:.+}", showontology) getters.HandleFunc("/o/{name:.+}", showontology)
getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile) getters.HandleFunc("/d/{xid:[[:alnum:].]+}", servefile)
getters.HandleFunc("/emu/{xid:[[:alnum:]_.-]+}", serveemu) getters.HandleFunc("/emu/{emu:[^.]*[^/]+}", serveemu)
getters.HandleFunc("/meme/{xid:[[:alnum:]_.-]+}", servememe) getters.HandleFunc("/meme/{meme:[^.]*[^/]+}", servememe)
getters.HandleFunc("/.well-known/webfinger", fingerlicker) getters.HandleFunc("/.well-known/webfinger", fingerlicker)
getters.HandleFunc("/server", serveractor) getters.HandleFunc("/server", serveractor)