enough of the honk filtering and censoring system (hfcs) to get going.

This commit is contained in:
Ted Unangst 2019-10-04 16:30:33 -04:00
parent f0dfb6c082
commit de36230cec
11 changed files with 261 additions and 241 deletions

View File

@ -203,7 +203,7 @@ func iszonked(userid int64, xid string) bool {
} }
func needxonk(user *WhatAbout, x *Honk) bool { func needxonk(user *WhatAbout, x *Honk) bool {
if thoudostbitethythumb(user.ID, x.Audience, x.XID) { if rejectnote(x) {
log.Printf("not saving thumb biter? %s via %s", x.XID, x.Honker) log.Printf("not saving thumb biter? %s via %s", x.XID, x.Honker)
return false return false
} }
@ -213,7 +213,7 @@ func needxonkid(user *WhatAbout, xid string) bool {
if strings.HasPrefix(xid, user.URL+"/") { if strings.HasPrefix(xid, user.URL+"/") {
return false return false
} }
if thoudostbitethythumb(user.ID, nil, xid) { if rejectorigin(user.ID, xid) {
log.Printf("don't need thumb biter? %s", xid) log.Printf("don't need thumb biter? %s", xid)
return false return false
} }

View File

@ -515,6 +515,7 @@ var stmtThumbBiters, stmtDeleteHonk, stmtDeleteDonks, stmtDeleteOnts, stmtSaveZo
var stmtGetZonkers, stmtRecentHonkers, stmtGetXonker, stmtSaveXonker, stmtDeleteXonker *sql.Stmt var stmtGetZonkers, stmtRecentHonkers, stmtGetXonker, stmtSaveXonker, stmtDeleteXonker *sql.Stmt
var stmtSelectOnts, stmtSaveOnt, stmtUpdateFlags, stmtClearFlags *sql.Stmt var stmtSelectOnts, stmtSaveOnt, stmtUpdateFlags, stmtClearFlags *sql.Stmt
var stmtHonksForUserFirstClass, stmtSaveMeta, stmtDeleteMeta, stmtUpdateHonk *sql.Stmt var stmtHonksForUserFirstClass, stmtSaveMeta, stmtDeleteMeta, stmtUpdateHonk *sql.Stmt
var stmtGetFilters *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)
@ -581,4 +582,5 @@ func prepareStatements(db *sql.DB) {
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 = ?")
stmtSelectOnts = preparetodie(db, "select distinct(ontology) from onts join honks on onts.honkid = honks.honkid where (honks.userid = ? or honks.whofore = 2)") stmtSelectOnts = preparetodie(db, "select distinct(ontology) from onts join honks on onts.honkid = honks.honkid where (honks.userid = ? or honks.whofore = 2)")
stmtGetFilters = preparetodie(db, "select hfcsid, json from hfcs where userid = ?")
} }

View File

@ -2,6 +2,8 @@ changelog
-- next -- next
+++ Add Honk Filtering and Censoring System (HFCS).
+ Inline images in received posts. + Inline images in received posts.
+ Times for events. + Times for events.

View File

@ -53,11 +53,6 @@ You can zonk anything you like (or dislike), either your own honk or
those of others that you're tired of seeing. Be advised that deletion those of others that you're tired of seeing. Be advised that deletion
works poorly in a federated environment. It's more like please disregard. works poorly in a federated environment. It's more like please disregard.
The zonkzone supports muting unwanted contacts. One may mute an actor
(zonker), a domain (zomain), thread (zonvoy), or word (zord).
Posts can be collapsed, but not hidden, by specifying a zilence regex.
-- privacy -- privacy
Posted honks are public. The federated environment lacks robust privacy Posted honks are public. The federated environment lacks robust privacy

159
fun.go
View File

@ -35,7 +35,7 @@ import (
func reverbolate(userid int64, honks []*Honk) { func reverbolate(userid int64, honks []*Honk) {
filt := htfilter.New() filt := htfilter.New()
filt.Imager = replaceimg filt.Imager = replaceimg
zilences := getzilences(userid) zilences := getfilters(userid, filtCollapse)
for _, h := range honks { for _, h := range honks {
h.What += "ed" h.What += "ed"
if h.What == "tonked" { if h.What == "tonked" {
@ -76,7 +76,7 @@ func reverbolate(userid int64, honks []*Honk) {
h.Open = "" h.Open = ""
} }
} else { } else {
if badword := unsee(zilences, h.Precis, h.Noise, h.Donks); badword != "" { if badword := unsee(zilences, h); badword != "" {
if h.Precis == "" { if h.Precis == "" {
h.Precis = badword h.Precis = badword
} }
@ -179,50 +179,6 @@ func translate(honk *Honk) {
honk.Onts = oneofakind(ontologies(honk.Noise)) honk.Onts = oneofakind(ontologies(honk.Noise))
} }
func unsee(zilences []*regexp.Regexp, precis string, noise string, donks []*Donk) string {
for _, z := range zilences {
if z.MatchString(precis) || z.MatchString(noise) {
if precis == "" {
w := z.String()
return w[6 : len(w)-3]
}
return precis
}
for _, d := range donks {
if z.MatchString(d.Desc) {
if precis == "" {
w := z.String()
return w[6 : len(w)-3]
}
return precis
}
}
}
return ""
}
func osmosis(honks []*Honk, userid int64) []*Honk {
zords := getzords(userid)
j := 0
outer:
for _, h := range honks {
for _, z := range zords {
if z.MatchString(h.Precis) || z.MatchString(h.Noise) {
continue outer
}
for _, d := range h.Donks {
if z.MatchString(d.Desc) {
continue outer
}
}
}
honks[j] = h
j++
}
honks = honks[0:j]
return honks
}
func shortxid(xid string) string { func shortxid(xid string) string {
idx := strings.LastIndexByte(xid, '/') idx := strings.LastIndexByte(xid, '/')
if idx == -1 { if idx == -1 {
@ -695,117 +651,6 @@ func makeitworksomehowwithoutregardforkeycontinuity(keyname string, r *http.Requ
return httpsig.VerifyRequest(r, payload, zaggy) return httpsig.VerifyRequest(r, payload, zaggy)
} }
var thumbbiters map[int64]map[string]bool
var zoggles map[int64]map[string]bool
var zordses map[int64][]*regexp.Regexp
var zilences map[int64][]*regexp.Regexp
var thumblock sync.Mutex
func bitethethumbs() {
rows, err := stmtThumbBiters.Query()
if err != nil {
log.Printf("error getting thumbbiters: %s", err)
return
}
defer rows.Close()
thumblock.Lock()
defer thumblock.Unlock()
thumbbiters = make(map[int64]map[string]bool)
zoggles = make(map[int64]map[string]bool)
zordses = make(map[int64][]*regexp.Regexp)
zilences = make(map[int64][]*regexp.Regexp)
for rows.Next() {
var userid int64
var name, wherefore string
err = rows.Scan(&userid, &name, &wherefore)
if err != nil {
log.Printf("error scanning zonker: %s", err)
continue
}
if wherefore == "zord" || wherefore == "zilence" {
zord := "\\b(?i:" + name + ")\\b"
re, err := regexp.Compile(zord)
if err != nil {
log.Printf("error compiling zord: %s", err)
} else {
if wherefore == "zord" {
zordses[userid] = append(zordses[userid], re)
} else {
zilences[userid] = append(zilences[userid], re)
}
}
}
if wherefore == "zoggle" {
m := zoggles[userid]
if m == nil {
m = make(map[string]bool)
zoggles[userid] = m
}
m[name] = true
}
if wherefore == "zonker" || wherefore == "zomain" {
m := thumbbiters[userid]
if m == nil {
m = make(map[string]bool)
thumbbiters[userid] = m
}
m[name] = true
}
}
}
func getzords(userid int64) []*regexp.Regexp {
thumblock.Lock()
defer thumblock.Unlock()
return zordses[userid]
}
func getzilences(userid int64) []*regexp.Regexp {
thumblock.Lock()
defer thumblock.Unlock()
return zilences[userid]
}
func thoudostbitethythumb(userid int64, who []string, objid string) bool {
thumblock.Lock()
biters := thumbbiters[userid]
thumblock.Unlock()
objwhere := originate(objid)
if objwhere != "" && biters[objwhere] {
log.Printf("thumbbiter: %s", objid)
return true
}
for _, w := range who {
if biters[w] {
log.Printf("thumbbiter: %s", w)
return true
}
where := originate(w)
if where != "" {
if biters[where] {
log.Printf("thumbbiter: %s", w)
return true
}
}
}
return false
}
func stealthmode(userid int64, r *http.Request) bool {
agent := r.UserAgent()
agent = originate(agent)
addr := r.Header.Get("X-Forwarded-For")
thumblock.Lock()
biters := thumbbiters[userid]
thumblock.Unlock()
fake := (agent != "" && biters[agent]) || (addr != "" && biters[addr])
if fake {
log.Printf("faking 404 for %s from %s", agent, addr)
}
return fake
}
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 {

174
hfcs.go
View File

@ -17,16 +17,176 @@ package main
import ( import (
"log" "log"
"net/http"
"regexp"
) )
func skipMedia(xonk *Honk) bool { type Filter struct {
thumblock.Lock() ID int64
goggles := zoggles[xonk.UserID] Actor string
thumblock.Unlock() IncludeAudience bool
Text string
IsAnnounce bool
Reject bool
SkipMedia bool
Hide bool
Collapse bool
Rewrite string
re_rewrite *regexp.Regexp
Replace string
}
if goggles[xonk.Honker] || goggles[xonk.Oonker] { type filtType uint
log.Printf("skipping media")
return true const (
filtNone filtType = iota
filtAny
filtReject
filtSkipMedia
filtHide
filtCollapse
filtRewrite
)
var filtNames = []string{"None", "Any", "Reject", "SkipMedia", "Hide", "Collapse", "Rewrite"}
func (ft filtType) String() string {
return filtNames[ft]
}
type afiltermap map[filtType][]*Filter
var filtcache = cacheNew(func(userid int64) (afiltermap, bool) {
rows, err := stmtGetFilters.Query(userid)
if err != nil {
log.Printf("error querying filters: %s", err)
return nil, false
}
defer rows.Close()
filtmap := make(afiltermap)
for rows.Next() {
filt := new(Filter)
var j string
var filterid int64
err = rows.Scan(&filterid, &j)
if err == nil {
err = unjsonify(j, filt)
}
if err != nil {
log.Printf("error scanning filter: %s", err)
continue
}
filt.ID = filterid
if filt.Reject {
filtmap[filtReject] = append(filtmap[filtReject], filt)
}
if filt.SkipMedia {
filtmap[filtSkipMedia] = append(filtmap[filtSkipMedia], filt)
}
if filt.Hide {
filtmap[filtHide] = append(filtmap[filtHide], filt)
}
if filt.Collapse {
filtmap[filtCollapse] = append(filtmap[filtCollapse], filt)
}
if filt.Rewrite != "" {
filtmap[filtRewrite] = append(filtmap[filtRewrite], filt)
}
}
return filtmap, true
})
func getfilters(userid int64, scope filtType) []*Filter {
var filtmap afiltermap
ok := filtcache.Get(userid, &filtmap)
if ok {
return filtmap[scope]
}
return nil
}
func rejectorigin(userid int64, origin string) bool {
if o := originate(origin); o != "" {
origin = o
}
filts := getfilters(userid, filtReject)
for _, f := range filts {
if f.Actor == origin {
log.Printf("rejecting origin: %s", origin)
return true
}
} }
return false return false
} }
func rejectactor(userid int64, actor string) bool {
origin := originate(actor)
filts := getfilters(userid, filtReject)
for _, f := range filts {
if f.Actor == actor || (origin != "" && f.Actor == origin) {
log.Printf("rejecting actor: %s", actor)
return true
}
}
return false
}
func stealthmode(userid int64, r *http.Request) bool {
agent := r.UserAgent()
agent = originate(agent)
if agent != "" {
fake := rejectorigin(userid, agent)
if fake {
log.Printf("faking 404 for %s", agent)
return fake
}
}
return false
}
func matchfilter(h *Honk, filts []*Filter) bool {
origin := originate(h.XID)
for _, f := range filts {
if f.Actor == origin || f.Actor == h.Honker {
return true
}
if f.Text != "" {
for _, d := range h.Donks {
if d.Desc == f.Text {
return true
}
}
}
}
return false
}
func rejectnote(xonk *Honk) bool {
filts := getfilters(xonk.UserID, filtReject)
return matchfilter(xonk, filts)
}
func skipMedia(xonk *Honk) bool {
filts := getfilters(xonk.UserID, filtSkipMedia)
return matchfilter(xonk, filts)
}
func unsee(filts []*Filter, h *Honk) string {
return ""
}
func osmosis(honks []*Honk, userid int64) []*Honk {
filts := getfilters(userid, filtHide)
j := 0
outer:
for _, h := range honks {
if matchfilter(h, filts) {
continue outer
}
honks[j] = h
j++
}
honks = honks[0:j]
return honks
}

View File

@ -8,6 +8,7 @@ create table zonkers (zonkerid integer primary key, userid integer, name text, w
create table doovers(dooverid integer primary key, dt text, tries integer, username text, rcpt text, msg blob); create table doovers(dooverid integer primary key, dt text, tries integer, username text, rcpt text, msg blob);
create table onts (ontology text, honkid integer); create table onts (ontology text, honkid integer);
create table honkmeta (honkid integer, genus text, json text); create table honkmeta (honkid integer, genus text, json text);
create table hfcs (hfcsid integer primary key, userid integer, json text);
create index idx_honksxid on honks(xid); create index idx_honksxid on honks(xid);
create index idx_honksconvoy on honks(convoy); create index idx_honksconvoy on honks(convoy);
@ -21,6 +22,7 @@ create index idx_filesurl on filemeta(url);
create index idx_ontology on onts(ontology); create index idx_ontology on onts(ontology);
create index idx_onthonkid on onts(honkid); create index idx_onthonkid on onts(honkid);
create index idx_honkmetaid on honkmeta(honkid); create index idx_honkmetaid on honkmeta(honkid);
create index idx_hfcsuser on hfcs(userid);
create table config (key text, value text); create table config (key text, value text);

View File

@ -221,12 +221,11 @@ func upgradedb() {
var xid, media string var xid, media string
var data []byte var data []byte
err = rows.Scan(&xid, &media, &data) err = rows.Scan(&xid, &media, &data)
if err != nil { if err == nil {
log.Fatal(err) _, err = tx.Exec("insert into filedata (xid, media, content) values (?, ?, ?)", xid, media, data)
} }
_, err = tx.Exec("insert into filedata (xid, media, content) values (?, ?, ?)", xid, media, data)
if err != nil { if err != nil {
log.Fatal(err) log.Fatalf("can't save filedata: %s", err)
} }
} }
rows.Close() rows.Close()
@ -263,8 +262,10 @@ func upgradedb() {
} }
for honkid, p := range places { for honkid, p := range places {
j, err := jsonify(p) j, err := jsonify(p)
_, err = tx.Exec("insert into honkmeta (honkid, genus, json) values (?, ?, ?)", if err == nil {
honkid, "place", j) _, err = tx.Exec("insert into honkmeta (honkid, genus, json) values (?, ?, ?)",
honkid, "place", j)
}
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -276,6 +277,66 @@ func upgradedb() {
doordie(db, "update config set value = 23 where key = 'dbversion'") doordie(db, "update config set value = 23 where key = 'dbversion'")
fallthrough fallthrough
case 23: case 23:
doordie(db, "create table hfcs (hfcsid integer primary key, userid integer, json text)")
doordie(db, "create index idx_hfcsuser on hfcs(userid)")
rows, err := db.Query("select userid, name, wherefore from zonkers where wherefore in ('zord', 'zilence', 'zoggle', 'zonker', 'zomain')")
if err != nil {
log.Fatalf("can't query zonkers: %s", err)
}
filtmap := make(map[int64][]*Filter)
for rows.Next() {
var userid int64
var name, wherefore string
err = rows.Scan(&userid, &name, &wherefore)
if err != nil {
log.Fatal("error scanning zonker: %s", err)
}
f := new(Filter)
switch wherefore {
case "zord":
f.Text = name
f.Hide = true
case "zilence":
f.Text = name
f.Collapse = true
case "zoggle":
f.Actor = name
f.SkipMedia = true
case "zonker":
f.Actor = name
f.IncludeAudience = true
f.Reject = true
case "zomain":
f.Actor = name
f.IncludeAudience = true
f.Reject = true
}
filtmap[userid] = append(filtmap[userid], f)
}
rows.Close()
tx, err := db.Begin()
if err != nil {
log.Fatalf("can't begin: %s", err)
}
for userid, filts := range filtmap {
for _, f := range filts {
j, err := jsonify(f)
if err == nil {
_, err = tx.Exec("insert into hfcs (userid, json) values (?, ?)", userid, j)
}
if err != nil {
log.Fatalf("can't save filter: %s", err)
}
}
}
err = tx.Commit()
if err != nil {
log.Fatalf("can't commit: %s", err)
}
doordie(db, "delete from zonkers where wherefore in ('zord', 'zilence', 'zoggle', 'zonker', 'zomain')")
doordie(db, "update config set value = 24 where key = 'dbversion'")
fallthrough
case 24:
default: default:
log.Fatalf("can't upgrade unknown version %d", dbversion) log.Fatalf("can't upgrade unknown version %d", dbversion)

View File

@ -73,7 +73,7 @@ var alreadyopendb *sql.DB
var dbname = "honk.db" var dbname = "honk.db"
var blobdbname = "blob.db" var blobdbname = "blob.db"
var stmtConfig *sql.Stmt var stmtConfig *sql.Stmt
var myVersion = 23 var myVersion = 24
func initdb() { func initdb() {
schema, err := ioutil.ReadFile("schema.sql") schema, err := ioutil.ReadFile("schema.sql")

View File

@ -2,37 +2,15 @@
<main> <main>
<div class="info"> <div class="info">
<p> <p>
<form action="/zonkzonk" method="POST"> Work in progress
<span class="title">it's zonking time!</span>
<input type="hidden" name="CSRF" value="{{ .ZonkCSRF }}">
<p>
<input tabindex=1 type="text" name="name" value="" autocomplete=off> - name
<p>
<input type="radio" id="iszonker" name="wherefore" value="zonker">
<label for="iszonker">Zonker</label>
<p>
<input type="radio" id="iszomain" name="wherefore" value="zomain">
<label for="iszomain">Zomain</label>
<p>
<input type="radio" id="iszonvoy" name="wherefore" value="zonvoy">
<label for="iszonvoy">Zonvoy</label>
<p>
<input type="radio" id="iszord" name="wherefore" value="zord">
<label for="iszord">Zord</label>
<p>
<input type="radio" id="iszilence" name="wherefore" value="zilence">
<label for="iszilence">Zilence</label>
<p>
<input type="radio" id="iszoggle" name="wherefore" value="zoggle">
<label for="iszoggle">Zoggle</label>
<p><br><button tabindex=1 name="zonk" value="zonk!">zonk!</button>
</form>
</div> </div>
{{ $zonkcsrf := .ZonkCSRF }} {{ $zonkcsrf := .ZonkCSRF }}
{{ range .Zonkers }} {{ range $how, $filters := .Filters }}
{{ range $filters }}
<section class="honk"> <section class="honk">
<p>What: {{ .Name }} <p>How: {{ $how }}
<p>Where: {{ .Wherefore }} {{ with .Actor }}<p>Who: {{ . }}{{ end }}
{{ with .Text }}<p>What: {{ . }}{{ end }}
<form action="/zonkzonk" method="POST"> <form action="/zonkzonk" method="POST">
<input type="hidden" name="CSRF" value="{{ $zonkcsrf }}"> <input type="hidden" name="CSRF" value="{{ $zonkcsrf }}">
<input type="hidden" name="zonkerid" value="{{ .ID }}"> <input type="hidden" name="zonkerid" value="{{ .ID }}">
@ -42,4 +20,5 @@
<p> <p>
</section> </section>
{{ end }} {{ end }}
{{ end }}
</main> </main>

44
web.go
View File

@ -307,8 +307,7 @@ func inbox(w http.ResponseWriter, r *http.Request) {
log.Printf("keyname actor mismatch: %s <> %s", keyname, who) log.Printf("keyname actor mismatch: %s <> %s", keyname, who)
return return
} }
objid, _ := j.GetString("id") if rejectactor(user.ID, who) {
if thoudostbitethythumb(user.ID, []string{who}, objid) {
log.Printf("ignoring thumb sucker %s", who) log.Printf("ignoring thumb sucker %s", who)
return return
} }
@ -1240,37 +1239,14 @@ func submithonker(w http.ResponseWriter, r *http.Request) {
func zonkzone(w http.ResponseWriter, r *http.Request) { func zonkzone(w http.ResponseWriter, r *http.Request) {
userinfo := login.GetUserInfo(r) userinfo := login.GetUserInfo(r)
rows, err := stmtGetZonkers.Query(userinfo.UserID)
if err != nil { var filters afiltermap
log.Printf("err: %s", err) filtcache.Get(userinfo.UserID, &filters)
return
}
defer rows.Close()
var zonkers []Zonker
for rows.Next() {
var z Zonker
rows.Scan(&z.ID, &z.Name, &z.Wherefore)
zonkers = append(zonkers, z)
}
sort.Slice(zonkers, func(i, j int) bool {
w1 := zonkers[i].Wherefore
w2 := zonkers[j].Wherefore
if w1 == w2 {
return zonkers[i].Name < zonkers[j].Name
}
if w1 == "zonvoy" {
w1 = "zzzzzzz"
}
if w2 == "zonvoy" {
w2 = "zzzzzzz"
}
return w1 < w2
})
templinfo := getInfo(r) templinfo := getInfo(r)
templinfo["Zonkers"] = zonkers templinfo["Filters"] = filters
templinfo["ZonkCSRF"] = login.GetCSRF("zonkzonk", r) templinfo["ZonkCSRF"] = login.GetCSRF("zonkzonk", r)
err = readviews.Execute(w, "zonkers.html", templinfo) err := readviews.Execute(w, "zonkers.html", templinfo)
if err != nil { if err != nil {
log.Print(err) log.Print(err)
} }
@ -1282,9 +1258,9 @@ func zonkzonk(w http.ResponseWriter, r *http.Request) {
if itsok == "iforgiveyou" { if itsok == "iforgiveyou" {
zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0) zonkerid, _ := strconv.ParseInt(r.FormValue("zonkerid"), 10, 0)
db := opendatabase() db := opendatabase()
db.Exec("delete from zonkers where userid = ? and zonkerid = ?", db.Exec("delete from hfcs where userid = ? and hfcsid = ?",
userinfo.UserID, zonkerid) userinfo.UserID, zonkerid)
bitethethumbs() filtcache.Clear(userinfo.UserID)
http.Redirect(w, r, "/zonkzone", http.StatusSeeOther) http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
return return
} }
@ -1308,7 +1284,7 @@ func zonkzonk(w http.ResponseWriter, r *http.Request) {
userinfo.UserID, name, wherefore) userinfo.UserID, name, wherefore)
if wherefore != "zonvoy" { if wherefore != "zonvoy" {
bitethethumbs() filtcache.Clear(userinfo.UserID)
} }
http.Redirect(w, r, "/zonkzone", http.StatusSeeOther) http.Redirect(w, r, "/zonkzone", http.StatusSeeOther)
@ -1550,8 +1526,6 @@ func serve() {
} }
} }
bitethethumbs()
mux := mux.NewRouter() mux := mux.NewRouter()
mux.Use(login.Checker) mux.Use(login.Checker)