all repos — honk @ 89ec1d7d87dc41bd924785c69cf0ab59581b7baf

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	"log"
 20	"net/http"
 21	"regexp"
 22	"sort"
 23	"time"
 24
 25	"humungus.tedunangst.com/r/webs/cache"
 26)
 27
 28type Filter struct {
 29	ID              int64      `json:"-"`
 30	Actions         []filtType `json:"-"`
 31	Name            string
 32	Date            time.Time
 33	Actor           string `json:",omitempty"`
 34	IncludeAudience bool   `json:",omitempty"`
 35	Text            string `json:",omitempty"`
 36	re_text         *regexp.Regexp
 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		log.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			log.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 filt.Text != "" {
112			filt.re_text, err = regexp.Compile("\\b(?i:" + filt.Text + ")\\b")
113			if err != nil {
114				log.Printf("error compiling filter text: %s", err)
115				continue
116			}
117		}
118		if filt.Rewrite != "" {
119			filt.re_rewrite, err = regexp.Compile("\\b(?i:" + filt.Rewrite + ")\\b")
120			if err != nil {
121				log.Printf("error compiling filter rewrite: %s", err)
122				continue
123			}
124		}
125		filt.ID = filterid
126		if filt.Reject {
127			filt.Actions = append(filt.Actions, filtReject)
128			filtmap[filtReject] = append(filtmap[filtReject], filt)
129		}
130		if filt.SkipMedia {
131			filt.Actions = append(filt.Actions, filtSkipMedia)
132			filtmap[filtSkipMedia] = append(filtmap[filtSkipMedia], filt)
133		}
134		if filt.Hide {
135			filt.Actions = append(filt.Actions, filtHide)
136			filtmap[filtHide] = append(filtmap[filtHide], filt)
137		}
138		if filt.Collapse {
139			filt.Actions = append(filt.Actions, filtCollapse)
140			filtmap[filtCollapse] = append(filtmap[filtCollapse], filt)
141		}
142		if filt.Rewrite != "" {
143			filt.Actions = append(filt.Actions, filtRewrite)
144			filtmap[filtRewrite] = append(filtmap[filtRewrite], filt)
145		}
146		filtmap[filtAny] = append(filtmap[filtAny], filt)
147	}
148	sorting := filtmap[filtAny]
149	sort.Slice(filtmap[filtAny], func(i, j int) bool {
150		return sorting[i].Name < sorting[j].Name
151	})
152	if !expflush.IsZero() {
153		dur := expflush.Sub(now)
154		go filtcacheclear(userid, dur)
155	}
156	return filtmap, true
157}
158
159func filtcacheclear(userid int64, dur time.Duration) {
160	time.Sleep(dur + time.Second)
161	filtInvalidator.Clear(userid)
162}
163
164func getfilters(userid int64, scope filtType) []*Filter {
165	var filtmap afiltermap
166	ok := filtcache.Get(userid, &filtmap)
167	if ok {
168		return filtmap[scope]
169	}
170	return nil
171}
172
173type arejectmap map[string][]*Filter
174
175var rejectAnyKey = "..."
176
177var rejectcache = cache.New(cache.Options{Filler: func(userid int64) (arejectmap, bool) {
178	m := make(arejectmap)
179	filts := getfilters(userid, filtReject)
180	for _, f := range filts {
181		if f.Text != "" {
182			key := rejectAnyKey
183			m[key] = append(m[key], f)
184			continue
185		}
186		if f.IsAnnounce && f.AnnounceOf != "" {
187			key := f.AnnounceOf
188			m[key] = append(m[key], f)
189		}
190		if f.Actor != "" {
191			key := f.Actor
192			m[key] = append(m[key], f)
193		}
194	}
195	return m, true
196}, Invalidator: &filtInvalidator})
197
198func rejectfilters(userid int64, name string) []*Filter {
199	var m arejectmap
200	rejectcache.Get(userid, &m)
201	return m[name]
202}
203
204func rejectorigin(userid int64, origin string, isannounce bool) bool {
205	if o := originate(origin); o != "" {
206		origin = o
207	}
208	filts := rejectfilters(userid, origin)
209	for _, f := range filts {
210		if isannounce && f.IsAnnounce {
211			if f.AnnounceOf == origin {
212				log.Printf("rejecting announce: %s", origin)
213				return true
214			}
215		}
216		if f.Actor == origin {
217			log.Printf("rejecting origin: %s", origin)
218			return true
219		}
220	}
221	return false
222}
223
224func rejectactor(userid int64, actor string) bool {
225	filts := rejectfilters(userid, actor)
226	for _, f := range filts {
227		if f.IsAnnounce {
228			continue
229		}
230		if f.Actor == actor {
231			log.Printf("rejecting actor: %s", actor)
232			return true
233		}
234	}
235	origin := originate(actor)
236	if origin == "" {
237		return false
238	}
239	filts = rejectfilters(userid, origin)
240	for _, f := range filts {
241		if f.IsAnnounce {
242			continue
243		}
244		if f.Actor == origin {
245			log.Printf("rejecting actor: %s", actor)
246			return true
247		}
248	}
249	return false
250}
251
252func stealthmode(userid int64, r *http.Request) bool {
253	agent := r.UserAgent()
254	agent = originate(agent)
255	if agent != "" {
256		fake := rejectorigin(userid, agent, false)
257		if fake {
258			log.Printf("faking 404 for %s", agent)
259			return true
260		}
261	}
262	return false
263}
264
265func matchfilter(h *Honk, f *Filter) bool {
266	return matchfilterX(h, f) != ""
267}
268
269func matchfilterX(h *Honk, f *Filter) string {
270	rv := ""
271	match := true
272	if match && f.Actor != "" {
273		match = false
274		if f.Actor == h.Honker || f.Actor == h.Oonker {
275			match = true
276			rv = f.Actor
277		}
278		if !match && (f.Actor == originate(h.Honker) ||
279			f.Actor == originate(h.Oonker) ||
280			f.Actor == originate(h.XID)) {
281			match = true
282			rv = f.Actor
283		}
284		if !match && f.IncludeAudience {
285			for _, a := range h.Audience {
286				if f.Actor == a || f.Actor == originate(a) {
287					match = true
288					rv = f.Actor
289					break
290				}
291			}
292		}
293	}
294	if match && f.IsAnnounce {
295		match = false
296		if (f.AnnounceOf == "" && h.Oonker != "") || f.AnnounceOf == h.Oonker ||
297			f.AnnounceOf == originate(h.Oonker) {
298			match = true
299			rv += " announce"
300		}
301	}
302	if match && f.Text != "" {
303		match = false
304		re := f.re_text
305		m := re.FindString(h.Precis)
306		if m == "" {
307			m = re.FindString(h.Noise)
308		}
309		if m == "" {
310			for _, d := range h.Donks {
311				m = re.FindString(d.Desc)
312				if m != "" {
313					break
314				}
315			}
316		}
317		if m != "" {
318			match = true
319			rv = m
320		}
321	}
322	if match {
323		return rv
324	}
325	return ""
326}
327
328func rejectxonk(xonk *Honk) bool {
329	var m arejectmap
330	rejectcache.Get(xonk.UserID, &m)
331	filts := m[rejectAnyKey]
332	filts = append(filts, m[xonk.Honker]...)
333	filts = append(filts, m[originate(xonk.Honker)]...)
334	filts = append(filts, m[xonk.Oonker]...)
335	filts = append(filts, m[originate(xonk.Oonker)]...)
336	for _, a := range xonk.Audience {
337		filts = append(filts, m[a]...)
338		filts = append(filts, m[originate(a)]...)
339	}
340	for _, f := range filts {
341		if cause := matchfilterX(xonk, f); cause != "" {
342			log.Printf("rejecting %s because %s", xonk.XID, cause)
343			return true
344		}
345	}
346	return false
347}
348
349func skipMedia(xonk *Honk) bool {
350	filts := getfilters(xonk.UserID, filtSkipMedia)
351	for _, f := range filts {
352		if matchfilter(xonk, f) {
353			return true
354		}
355	}
356	return false
357}
358
359func unsee(honks []*Honk, userid int64) {
360	if userid != -1 {
361		colfilts := getfilters(userid, filtCollapse)
362		rwfilts := getfilters(userid, filtRewrite)
363		for _, h := range honks {
364			for _, f := range colfilts {
365				if bad := matchfilterX(h, f); bad != "" {
366					if h.Precis == "" {
367						h.Precis = bad
368					}
369					h.Open = ""
370					break
371				}
372			}
373			if h.Open == "open" && h.Precis == "unspecified horror" {
374				h.Precis = ""
375			}
376			for _, f := range rwfilts {
377				if matchfilter(h, f) {
378					h.Noise = f.re_rewrite.ReplaceAllString(h.Noise, f.Replace)
379				}
380			}
381			if len(h.Noise) > 6000 && h.Open == "open" {
382				if h.Precis == "" {
383					h.Precis = "really freaking long"
384				}
385				h.Open = ""
386			}
387		}
388	}
389}
390
391var untagged = cache.New(cache.Options{Filler: func(userid int64) (map[string]bool, bool) {
392	rows, err := stmtUntagged.Query(userid)
393	if err != nil {
394		log.Printf("error query untagged: %s", err)
395		return nil, false
396	}
397	defer rows.Close()
398	bad := make(map[string]bool)
399	for rows.Next() {
400		var xid, rid string
401		var flags int64
402		err = rows.Scan(&xid, &rid, &flags)
403		if err != nil {
404			log.Printf("error scanning untag: %s", err)
405			continue
406		}
407		if flags&flagIsUntagged != 0 {
408			bad[xid] = true
409		}
410		if bad[rid] {
411			bad[xid] = true
412		}
413	}
414	return bad, true
415}})
416
417func osmosis(honks []*Honk, userid int64, withfilt bool) []*Honk {
418	var badparents map[string]bool
419	untagged.GetAndLock(userid, &badparents)
420	j := 0
421	reversehonks(honks)
422	for _, h := range honks {
423		if badparents[h.RID] {
424			badparents[h.XID] = true
425			continue
426		}
427		honks[j] = h
428		j++
429	}
430	untagged.Unlock()
431	honks = honks[0:j]
432	reversehonks(honks)
433	if !withfilt {
434		return honks
435	}
436	filts := getfilters(userid, filtHide)
437	j = 0
438outer:
439	for _, h := range honks {
440		for _, f := range filts {
441			if matchfilter(h, f) {
442				continue outer
443			}
444		}
445		honks[j] = h
446		j++
447	}
448	honks = honks[0:j]
449	return honks
450}