all repos — honk @ c973e951ec23543966278c98ab77892435fcce08

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 != "" && 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 != "" && 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 && f.Text == "." {
338		match = false
339		if h.Precis != "" {
340			match = true
341			rv = h.Precis
342		}
343	}
344	if match {
345		return rv
346	}
347	return ""
348}
349
350func rejectxonk(xonk *Honk) bool {
351	var m arejectmap
352	rejectcache.Get(xonk.UserID, &m)
353	filts := m[rejectAnyKey]
354	filts = append(filts, m[xonk.Honker]...)
355	filts = append(filts, m[originate(xonk.Honker)]...)
356	filts = append(filts, m[xonk.Oonker]...)
357	filts = append(filts, m[originate(xonk.Oonker)]...)
358	for _, a := range xonk.Audience {
359		filts = append(filts, m[a]...)
360		filts = append(filts, m[originate(a)]...)
361	}
362	for _, f := range filts {
363		if cause := matchfilterX(xonk, f); cause != "" {
364			ilog.Printf("rejecting %s because %s", xonk.XID, cause)
365			return true
366		}
367	}
368	return false
369}
370
371func skipMedia(xonk *Honk) bool {
372	filts := getfilters(xonk.UserID, filtSkipMedia)
373	for _, f := range filts {
374		if matchfilter(xonk, f) {
375			return true
376		}
377	}
378	return false
379}
380
381func unsee(honks []*Honk, userid int64) {
382	if userid != -1 {
383		colfilts := getfilters(userid, filtCollapse)
384		rwfilts := getfilters(userid, filtRewrite)
385		for _, h := range honks {
386			for _, f := range colfilts {
387				if bad := matchfilterX(h, f); bad != "" {
388					if h.Precis == "" {
389						h.Precis = bad
390					}
391					h.Open = ""
392					break
393				}
394			}
395			if h.Open == "open" && h.Precis == "unspecified horror" {
396				h.Precis = ""
397			}
398			for _, f := range rwfilts {
399				if matchfilter(h, f) {
400					h.Noise = f.re_rewrite.ReplaceAllString(h.Noise, f.Replace)
401				}
402			}
403			if len(h.Noise) > 6000 && h.Open == "open" {
404				if h.Precis == "" {
405					h.Precis = "really freaking long"
406				}
407				h.Open = ""
408			}
409		}
410	}
411}
412
413var untagged = cache.New(cache.Options{Filler: func(userid int64) (map[string]bool, bool) {
414	rows, err := stmtUntagged.Query(userid)
415	if err != nil {
416		elog.Printf("error query untagged: %s", err)
417		return nil, false
418	}
419	defer rows.Close()
420	bad := make(map[string]bool)
421	for rows.Next() {
422		var xid, rid string
423		var flags int64
424		err = rows.Scan(&xid, &rid, &flags)
425		if err != nil {
426			elog.Printf("error scanning untag: %s", err)
427			continue
428		}
429		if flags&flagIsUntagged != 0 {
430			bad[xid] = true
431		}
432		if bad[rid] {
433			bad[xid] = true
434		}
435	}
436	return bad, true
437}})
438
439func osmosis(honks []*Honk, userid int64, withfilt bool) []*Honk {
440	var badparents map[string]bool
441	untagged.GetAndLock(userid, &badparents)
442	j := 0
443	reversehonks(honks)
444	for _, h := range honks {
445		if badparents[h.RID] {
446			badparents[h.XID] = true
447			continue
448		}
449		honks[j] = h
450		j++
451	}
452	untagged.Unlock()
453	honks = honks[0:j]
454	reversehonks(honks)
455	if !withfilt {
456		return honks
457	}
458	filts := getfilters(userid, filtHide)
459	j = 0
460outer:
461	for _, h := range honks {
462		for _, f := range filts {
463			if matchfilter(h, f) {
464				continue outer
465			}
466		}
467		honks[j] = h
468		j++
469	}
470	honks = honks[0:j]
471	return honks
472}