all repos — honk @ v0.9.91

my fork of honk

hfcs.go (view raw)

  1//
  2// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
  3//
  4// Permission to use, copy, modify, and distribute this software for any
  5// purpose with or without fee is hereby granted, provided that the above
  6// copyright notice and this permission notice appear in all copies.
  7//
  8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 15
 16package main
 17
 18import (
 19	"net/http"
 20	"regexp"
 21	"sort"
 22	"time"
 23
 24	"humungus.tedunangst.com/r/webs/cache"
 25)
 26
 27type Filter struct {
 28	ID              int64      `json:"-"`
 29	Actions         []filtType `json:"-"`
 30	Name            string
 31	Date            time.Time
 32	Actor           string `json:",omitempty"`
 33	IncludeAudience bool   `json:",omitempty"`
 34	Text            string `json:",omitempty"`
 35	re_text         *regexp.Regexp
 36	IsReply         bool   `json:",omitempty"`
 37	IsAnnounce      bool   `json:",omitempty"`
 38	AnnounceOf      string `json:",omitempty"`
 39	Reject          bool   `json:",omitempty"`
 40	SkipMedia       bool   `json:",omitempty"`
 41	Hide            bool   `json:",omitempty"`
 42	Collapse        bool   `json:",omitempty"`
 43	Rewrite         string `json:",omitempty"`
 44	re_rewrite      *regexp.Regexp
 45	Replace         string `json:",omitempty"`
 46	Expiration      time.Time
 47	Notes           string
 48}
 49
 50type filtType uint
 51
 52const (
 53	filtNone filtType = iota
 54	filtAny
 55	filtReject
 56	filtSkipMedia
 57	filtHide
 58	filtCollapse
 59	filtRewrite
 60)
 61
 62var filtNames = []string{"None", "Any", "Reject", "SkipMedia", "Hide", "Collapse", "Rewrite"}
 63
 64func (ft filtType) String() string {
 65	return filtNames[ft]
 66}
 67
 68type afiltermap map[filtType][]*Filter
 69
 70var filtInvalidator cache.Invalidator
 71var filtcache *cache.Cache
 72
 73func init() {
 74	// resolve init loop
 75	filtcache = cache.New(cache.Options{Filler: filtcachefiller, Invalidator: &filtInvalidator})
 76}
 77
 78func filtcachefiller(userid int64) (afiltermap, bool) {
 79	rows, err := stmtGetFilters.Query(userid)
 80	if err != nil {
 81		elog.Printf("error querying filters: %s", err)
 82		return nil, false
 83	}
 84	defer rows.Close()
 85
 86	now := time.Now()
 87
 88	var expflush time.Time
 89
 90	filtmap := make(afiltermap)
 91	for rows.Next() {
 92		filt := new(Filter)
 93		var j string
 94		var filterid int64
 95		err = rows.Scan(&filterid, &j)
 96		if err == nil {
 97			err = unjsonify(j, filt)
 98		}
 99		if err != nil {
100			elog.Printf("error scanning filter: %s", err)
101			continue
102		}
103		if !filt.Expiration.IsZero() {
104			if filt.Expiration.Before(now) {
105				continue
106			}
107			if expflush.IsZero() || filt.Expiration.Before(expflush) {
108				expflush = filt.Expiration
109			}
110		}
111		if t := filt.Text; t != "" && t != "." {
112			wordfront := t[0] != '#'
113			wordtail := true
114			t = "(?i:" + t + ")"
115			if wordfront {
116				t = "\\b" + t
117			}
118			if wordtail {
119				t = t + "\\b"
120			}
121			filt.re_text, err = regexp.Compile(t)
122			if err != nil {
123				elog.Printf("error compiling filter text: %s", err)
124				continue
125			}
126		}
127		if t := filt.Rewrite; t != "" {
128			wordfront := t[0] != '#'
129			wordtail := true
130			t = "(?i:" + t + ")"
131			if wordfront {
132				t = "\\b" + t
133			}
134			if wordtail {
135				t = t + "\\b"
136			}
137			filt.re_rewrite, err = regexp.Compile(t)
138			if err != nil {
139				elog.Printf("error compiling filter rewrite: %s", err)
140				continue
141			}
142		}
143		filt.ID = filterid
144		if filt.Reject {
145			filt.Actions = append(filt.Actions, filtReject)
146			filtmap[filtReject] = append(filtmap[filtReject], filt)
147		}
148		if filt.SkipMedia {
149			filt.Actions = append(filt.Actions, filtSkipMedia)
150			filtmap[filtSkipMedia] = append(filtmap[filtSkipMedia], filt)
151		}
152		if filt.Hide {
153			filt.Actions = append(filt.Actions, filtHide)
154			filtmap[filtHide] = append(filtmap[filtHide], filt)
155		}
156		if filt.Collapse {
157			filt.Actions = append(filt.Actions, filtCollapse)
158			filtmap[filtCollapse] = append(filtmap[filtCollapse], filt)
159		}
160		if filt.Rewrite != "" {
161			filt.Actions = append(filt.Actions, filtRewrite)
162			filtmap[filtRewrite] = append(filtmap[filtRewrite], filt)
163		}
164		filtmap[filtAny] = append(filtmap[filtAny], filt)
165	}
166	sorting := filtmap[filtAny]
167	sort.Slice(filtmap[filtAny], func(i, j int) bool {
168		return sorting[i].Name < sorting[j].Name
169	})
170	if !expflush.IsZero() {
171		dur := expflush.Sub(now)
172		go filtcacheclear(userid, dur)
173	}
174	return filtmap, true
175}
176
177func filtcacheclear(userid int64, dur time.Duration) {
178	time.Sleep(dur + time.Second)
179	filtInvalidator.Clear(userid)
180}
181
182func getfilters(userid int64, scope filtType) []*Filter {
183	var filtmap afiltermap
184	ok := filtcache.Get(userid, &filtmap)
185	if ok {
186		return filtmap[scope]
187	}
188	return nil
189}
190
191type arejectmap map[string][]*Filter
192
193var rejectAnyKey = "..."
194
195var rejectcache = cache.New(cache.Options{Filler: func(userid int64) (arejectmap, bool) {
196	m := make(arejectmap)
197	filts := getfilters(userid, filtReject)
198	for _, f := range filts {
199		if f.Text != "" {
200			key := rejectAnyKey
201			m[key] = append(m[key], f)
202			continue
203		}
204		if f.IsAnnounce && f.AnnounceOf != "" {
205			key := f.AnnounceOf
206			m[key] = append(m[key], f)
207		}
208		if f.Actor != "" {
209			key := f.Actor
210			m[key] = append(m[key], f)
211		}
212	}
213	return m, true
214}, Invalidator: &filtInvalidator})
215
216func rejectfilters(userid int64, name string) []*Filter {
217	var m arejectmap
218	rejectcache.Get(userid, &m)
219	return m[name]
220}
221
222func rejectorigin(userid int64, origin string, isannounce bool) bool {
223	if o := originate(origin); o != "" {
224		origin = o
225	}
226	filts := rejectfilters(userid, origin)
227	for _, f := range filts {
228		if isannounce && f.IsAnnounce {
229			if f.AnnounceOf == origin {
230				return true
231			}
232		}
233		if f.Actor == origin {
234			return true
235		}
236	}
237	return false
238}
239
240func rejectactor(userid int64, actor string) bool {
241	filts := rejectfilters(userid, actor)
242	for _, f := range filts {
243		if f.IsAnnounce {
244			continue
245		}
246		if f.Actor == actor {
247			ilog.Printf("rejecting actor: %s", actor)
248			return true
249		}
250	}
251	origin := originate(actor)
252	if origin == "" {
253		return false
254	}
255	filts = rejectfilters(userid, origin)
256	for _, f := range filts {
257		if f.IsAnnounce {
258			continue
259		}
260		if f.Actor == origin {
261			ilog.Printf("rejecting actor: %s", actor)
262			return true
263		}
264	}
265	return false
266}
267
268func stealthmode(userid int64, r *http.Request) bool {
269	agent := r.UserAgent()
270	agent = originate(agent)
271	if agent != "" {
272		fake := rejectorigin(userid, agent, false)
273		if fake {
274			ilog.Printf("faking 404 for %s", agent)
275			return true
276		}
277	}
278	return false
279}
280
281func matchfilter(h *Honk, f *Filter) bool {
282	return matchfilterX(h, f) != ""
283}
284
285func matchfilterX(h *Honk, f *Filter) string {
286	rv := ""
287	match := true
288	if match && f.Actor != "" {
289		match = false
290		if f.Actor == h.Honker || f.Actor == h.Oonker {
291			match = true
292			rv = f.Actor
293		}
294		if !match && (f.Actor == originate(h.Honker) ||
295			f.Actor == originate(h.Oonker) ||
296			f.Actor == originate(h.XID)) {
297			match = true
298			rv = f.Actor
299		}
300		if !match && f.IncludeAudience {
301			for _, a := range h.Audience {
302				if f.Actor == a || f.Actor == originate(a) {
303					match = true
304					rv = f.Actor
305					break
306				}
307			}
308		}
309	}
310	if match && f.IsReply {
311		match = false
312		if h.RID != "" {
313			match = true
314			rv += " reply"
315		}
316	}
317	if match && f.IsAnnounce {
318		match = false
319		if (f.AnnounceOf == "" && h.Oonker != "") || f.AnnounceOf == h.Oonker ||
320			f.AnnounceOf == originate(h.Oonker) {
321			match = true
322			rv += " announce"
323		}
324	}
325	if match && f.Text != "" && f.Text != "." {
326		match = false
327		re := f.re_text
328		m := re.FindString(h.Precis)
329		if m == "" {
330			m = re.FindString(h.Noise)
331		}
332		if m == "" {
333			for _, d := range h.Donks {
334				m = re.FindString(d.Desc)
335				if m != "" {
336					break
337				}
338			}
339		}
340		if m != "" {
341			match = true
342			rv = m
343		}
344	}
345	if match && f.Text == "." {
346		match = false
347		if h.Precis != "" {
348			match = true
349			rv = h.Precis
350		}
351	}
352	if match {
353		return rv
354	}
355	return ""
356}
357
358func rejectxonk(xonk *Honk) bool {
359	var m arejectmap
360	rejectcache.Get(xonk.UserID, &m)
361	filts := m[rejectAnyKey]
362	filts = append(filts, m[xonk.Honker]...)
363	filts = append(filts, m[originate(xonk.Honker)]...)
364	filts = append(filts, m[xonk.Oonker]...)
365	filts = append(filts, m[originate(xonk.Oonker)]...)
366	for _, a := range xonk.Audience {
367		filts = append(filts, m[a]...)
368		filts = append(filts, m[originate(a)]...)
369	}
370	for _, f := range filts {
371		if cause := matchfilterX(xonk, f); cause != "" {
372			ilog.Printf("rejecting %s because %s", xonk.XID, cause)
373			return true
374		}
375	}
376	return false
377}
378
379func skipMedia(xonk *Honk) bool {
380	filts := getfilters(xonk.UserID, filtSkipMedia)
381	for _, f := range filts {
382		if matchfilter(xonk, f) {
383			return true
384		}
385	}
386	return false
387}
388
389func unsee(honks []*Honk, userid int64) {
390	if userid != -1 {
391		colfilts := getfilters(userid, filtCollapse)
392		rwfilts := getfilters(userid, filtRewrite)
393		for _, h := range honks {
394			for _, f := range colfilts {
395				if bad := matchfilterX(h, f); bad != "" {
396					if h.Precis == "" {
397						h.Precis = bad
398					}
399					h.Open = ""
400					break
401				}
402			}
403			if h.Open == "open" && h.Precis == "unspecified horror" {
404				h.Precis = ""
405			}
406			for _, f := range rwfilts {
407				if matchfilter(h, f) {
408					h.Noise = f.re_rewrite.ReplaceAllString(h.Noise, f.Replace)
409				}
410			}
411			if len(h.Noise) > 6000 && h.Open == "open" {
412				if h.Precis == "" {
413					h.Precis = "really freaking long"
414				}
415				h.Open = ""
416			}
417		}
418	} else {
419		for _, h := range honks {
420			if h.Precis != "" {
421				h.Open = ""
422			}
423		}
424	}
425}
426
427var untagged = cache.New(cache.Options{Filler: func(userid int64) (map[string]bool, bool) {
428	rows, err := stmtUntagged.Query(userid)
429	if err != nil {
430		elog.Printf("error query untagged: %s", err)
431		return nil, false
432	}
433	defer rows.Close()
434	bad := make(map[string]bool)
435	for rows.Next() {
436		var xid, rid string
437		var flags int64
438		err = rows.Scan(&xid, &rid, &flags)
439		if err != nil {
440			elog.Printf("error scanning untag: %s", err)
441			continue
442		}
443		if flags&flagIsUntagged != 0 {
444			bad[xid] = true
445		}
446		if bad[rid] {
447			bad[xid] = true
448		}
449	}
450	return bad, true
451}})
452
453func osmosis(honks []*Honk, userid int64, withfilt bool) []*Honk {
454	var badparents map[string]bool
455	untagged.GetAndLock(userid, &badparents)
456	j := 0
457	reversehonks(honks)
458	for _, h := range honks {
459		if badparents[h.RID] {
460			badparents[h.XID] = true
461			continue
462		}
463		honks[j] = h
464		j++
465	}
466	untagged.Unlock()
467	honks = honks[0:j]
468	reversehonks(honks)
469	if !withfilt {
470		return honks
471	}
472	filts := getfilters(userid, filtHide)
473	j = 0
474outer:
475	for _, h := range honks {
476		for _, f := range filts {
477			if matchfilter(h, f) {
478				continue outer
479			}
480		}
481		honks[j] = h
482		j++
483	}
484	honks = honks[0:j]
485	return honks
486}