all repos — honk @ 41deae42c872cab5b3758e610a0564a5137e8644

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