all repos — honk @ 96f64f277852713ab92a6c67d06313e2d947c0e8

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 filtcache *cache.Cache
 71
 72func init() {
 73	// resolve init loop
 74	filtcache = cache.New(cache.Options{Filler: filtcachefiller})
 75}
 76
 77func filtcachefiller(userid int64) (afiltermap, bool) {
 78	rows, err := stmtGetFilters.Query(userid)
 79	if err != nil {
 80		log.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			log.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 filt.Text != "" {
111			filt.re_text, err = regexp.Compile("\\b(?i:" + filt.Text + ")\\b")
112			if err != nil {
113				log.Printf("error compiling filter text: %s", err)
114				continue
115			}
116		}
117		if filt.Rewrite != "" {
118			filt.re_rewrite, err = regexp.Compile("\\b(?i:" + filt.Rewrite + ")\\b")
119			if err != nil {
120				log.Printf("error compiling filter rewrite: %s", err)
121				continue
122			}
123		}
124		filt.ID = filterid
125		if filt.Reject {
126			filt.Actions = append(filt.Actions, filtReject)
127			filtmap[filtReject] = append(filtmap[filtReject], filt)
128		}
129		if filt.SkipMedia {
130			filt.Actions = append(filt.Actions, filtSkipMedia)
131			filtmap[filtSkipMedia] = append(filtmap[filtSkipMedia], filt)
132		}
133		if filt.Hide {
134			filt.Actions = append(filt.Actions, filtHide)
135			filtmap[filtHide] = append(filtmap[filtHide], filt)
136		}
137		if filt.Collapse {
138			filt.Actions = append(filt.Actions, filtCollapse)
139			filtmap[filtCollapse] = append(filtmap[filtCollapse], filt)
140		}
141		if filt.Rewrite != "" {
142			filt.Actions = append(filt.Actions, filtRewrite)
143			filtmap[filtRewrite] = append(filtmap[filtRewrite], filt)
144		}
145		filtmap[filtAny] = append(filtmap[filtAny], filt)
146	}
147	sorting := filtmap[filtAny]
148	sort.Slice(filtmap[filtAny], func(i, j int) bool {
149		return sorting[i].Name < sorting[j].Name
150	})
151	if !expflush.IsZero() {
152		dur := expflush.Sub(now)
153		go filtcacheclear(userid, dur)
154	}
155	return filtmap, true
156}
157
158func filtcacheclear(userid int64, dur time.Duration) {
159	time.Sleep(dur + time.Second)
160	filtcache.Clear(userid)
161}
162
163func getfilters(userid int64, scope filtType) []*Filter {
164	var filtmap afiltermap
165	ok := filtcache.Get(userid, &filtmap)
166	if ok {
167		return filtmap[scope]
168	}
169	return nil
170}
171
172func rejectorigin(userid int64, origin string, isannounce bool) bool {
173	if o := originate(origin); o != "" {
174		origin = o
175	}
176	filts := getfilters(userid, filtReject)
177	for _, f := range filts {
178		if f.Text != "" {
179			continue
180		}
181		if f.IsAnnounce {
182			if !isannounce {
183				continue
184			}
185			if f.AnnounceOf == origin {
186				log.Printf("rejecting announce: %s", origin)
187				return true
188			}
189		}
190		if f.Actor == origin {
191			log.Printf("rejecting origin: %s", origin)
192			return true
193		}
194	}
195	return false
196}
197
198func rejectactor(userid int64, actor string) bool {
199	origin := originate(actor)
200	filts := getfilters(userid, filtReject)
201	for _, f := range filts {
202		if f.IsAnnounce || f.Text != "" {
203			continue
204		}
205		if f.Actor == actor || (origin != "" && f.Actor == origin) {
206			log.Printf("rejecting actor: %s", actor)
207			return true
208		}
209	}
210	return false
211}
212
213func stealthmode(userid int64, r *http.Request) bool {
214	agent := r.UserAgent()
215	agent = originate(agent)
216	if agent != "" {
217		fake := rejectorigin(userid, agent, false)
218		if fake {
219			log.Printf("faking 404 for %s", agent)
220			return true
221		}
222	}
223	return false
224}
225
226func matchfilter(h *Honk, f *Filter) bool {
227	return matchfilterX(h, f) != ""
228}
229
230func matchfilterX(h *Honk, f *Filter) string {
231	rv := ""
232	match := true
233	if match && f.Actor != "" {
234		match = false
235		if f.Actor == h.Honker || f.Actor == h.Oonker {
236			match = true
237			rv = f.Actor
238		}
239		if !match && (f.Actor == originate(h.Honker) ||
240			f.Actor == originate(h.Oonker) ||
241			f.Actor == originate(h.XID)) {
242			match = true
243			rv = f.Actor
244		}
245		if !match && f.IncludeAudience {
246			for _, a := range h.Audience {
247				if f.Actor == a || f.Actor == originate(a) {
248					match = true
249					rv = f.Actor
250					break
251				}
252			}
253		}
254	}
255	if match && f.IsAnnounce {
256		match = false
257		if (f.AnnounceOf == "" && h.Oonker != "") || f.AnnounceOf == h.Oonker ||
258			f.AnnounceOf == originate(h.Oonker) {
259			match = true
260			rv += " announce"
261		}
262	}
263	if match && f.Text != "" {
264		match = false
265		re := f.re_text
266		m := re.FindString(h.Precis)
267		if m == "" {
268			m = re.FindString(h.Noise)
269		}
270		if m == "" {
271			for _, d := range h.Donks {
272				m = re.FindString(d.Desc)
273				if m != "" {
274					break
275				}
276			}
277		}
278		if m != "" {
279			match = true
280			rv = m
281		}
282	}
283	if match {
284		return rv
285	}
286	return ""
287}
288
289func rejectxonk(xonk *Honk) bool {
290	filts := getfilters(xonk.UserID, filtReject)
291	for _, f := range filts {
292		if matchfilter(xonk, f) {
293			log.Printf("rejecting %s because %s", xonk.XID, f.Actor)
294			return true
295		}
296	}
297	return false
298}
299
300func skipMedia(xonk *Honk) bool {
301	filts := getfilters(xonk.UserID, filtSkipMedia)
302	for _, f := range filts {
303		if matchfilter(xonk, f) {
304			return true
305		}
306	}
307	return false
308}
309
310func unsee(userid int64, h *Honk) {
311	filts := getfilters(userid, filtCollapse)
312	for _, f := range filts {
313		if bad := matchfilterX(h, f); bad != "" {
314			if h.Precis == "" {
315				h.Precis = bad
316			}
317			h.Open = ""
318			break
319		}
320	}
321	filts = getfilters(userid, filtRewrite)
322	for _, f := range filts {
323		if matchfilter(h, f) {
324			h.Noise = f.re_rewrite.ReplaceAllString(h.Noise, f.Replace)
325		}
326	}
327}
328
329var untagged = cache.New(cache.Options{Filler: func(userid int64) (map[string]bool, bool) {
330	rows, err := stmtUntagged.Query(userid)
331	if err != nil {
332		log.Printf("error query untagged: %s", err)
333		return nil, false
334	}
335	defer rows.Close()
336	bad := make(map[string]bool)
337	for rows.Next() {
338		var xid, rid string
339		var flags int64
340		err = rows.Scan(&xid, &rid, &flags)
341		if err != nil {
342			log.Printf("error scanning untag: %s", err)
343			continue
344		}
345		if flags&flagIsUntagged != 0 {
346			bad[xid] = true
347		}
348		if bad[rid] {
349			bad[xid] = true
350		}
351	}
352	return bad, true
353}})
354
355func osmosis(honks []*Honk, userid int64, withfilt bool) []*Honk {
356	var badparents map[string]bool
357	untagged.GetAndLock(userid, &badparents)
358	j := 0
359	reversehonks(honks)
360	for _, h := range honks {
361		if badparents[h.RID] {
362			badparents[h.XID] = true
363			continue
364		}
365		honks[j] = h
366		j++
367	}
368	untagged.Unlock()
369	honks = honks[0:j]
370	reversehonks(honks)
371	if !withfilt {
372		return honks
373	}
374	filts := getfilters(userid, filtHide)
375	j = 0
376outer:
377	for _, h := range honks {
378		for _, f := range filts {
379			if matchfilter(h, f) {
380				continue outer
381			}
382		}
383		honks[j] = h
384		j++
385	}
386	honks = honks[0:j]
387	return honks
388}