all repos — honk @ 116abad45b34c4d5591e913d57c4a4f51155a4b2

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) bool {
173	if o := originate(origin); o != "" {
174		origin = o
175	}
176	filts := getfilters(userid, filtReject)
177	for _, f := range filts {
178		if f.IsAnnounce || f.Text != "" {
179			continue
180		}
181		if f.Actor == origin {
182			log.Printf("rejecting origin: %s", origin)
183			return true
184		}
185	}
186	return false
187}
188
189func rejectactor(userid int64, actor string) bool {
190	origin := originate(actor)
191	filts := getfilters(userid, filtReject)
192	for _, f := range filts {
193		if f.IsAnnounce || f.Text != "" {
194			continue
195		}
196		if f.Actor == actor || (origin != "" && f.Actor == origin) {
197			log.Printf("rejecting actor: %s", actor)
198			return true
199		}
200	}
201	return false
202}
203
204func stealthmode(userid int64, r *http.Request) bool {
205	agent := r.UserAgent()
206	agent = originate(agent)
207	if agent != "" {
208		fake := rejectorigin(userid, agent)
209		if fake {
210			log.Printf("faking 404 for %s", agent)
211			return true
212		}
213	}
214	return false
215}
216
217func matchfilter(h *Honk, f *Filter) bool {
218	return matchfilterX(h, f) != ""
219}
220
221func matchfilterX(h *Honk, f *Filter) string {
222	rv := ""
223	match := true
224	if match && f.Actor != "" {
225		match = false
226		if f.Actor == h.Honker || f.Actor == h.Oonker {
227			match = true
228			rv = f.Actor
229		}
230		if !match && (f.Actor == originate(h.Honker) ||
231			f.Actor == originate(h.Oonker) ||
232			f.Actor == originate(h.XID)) {
233			match = true
234			rv = f.Actor
235		}
236		if !match && f.IncludeAudience {
237			for _, a := range h.Audience {
238				if f.Actor == a || f.Actor == originate(a) {
239					match = true
240					rv = f.Actor
241					break
242				}
243			}
244		}
245	}
246	if match && f.IsAnnounce {
247		match = false
248		if (f.AnnounceOf == "" && h.Oonker != "") || f.AnnounceOf == h.Oonker ||
249			f.AnnounceOf == originate(h.Oonker) {
250			match = true
251			rv += " announce"
252		}
253	}
254	if match && f.Text != "" {
255		match = false
256		re := f.re_text
257		m := re.FindString(h.Precis)
258		if m == "" {
259			m = re.FindString(h.Noise)
260		}
261		if m == "" {
262			for _, d := range h.Donks {
263				m = re.FindString(d.Desc)
264				if m != "" {
265					break
266				}
267			}
268		}
269		if m != "" {
270			match = true
271			rv = m
272		}
273	}
274	if match {
275		return rv
276	}
277	return ""
278}
279
280func rejectxonk(xonk *Honk) bool {
281	filts := getfilters(xonk.UserID, filtReject)
282	for _, f := range filts {
283		if matchfilter(xonk, f) {
284			log.Printf("rejecting %s because %s", xonk.XID, f.Actor)
285			return true
286		}
287	}
288	return false
289}
290
291func skipMedia(xonk *Honk) bool {
292	filts := getfilters(xonk.UserID, filtSkipMedia)
293	for _, f := range filts {
294		if matchfilter(xonk, f) {
295			return true
296		}
297	}
298	return false
299}
300
301func unsee(userid int64, h *Honk) {
302	filts := getfilters(userid, filtCollapse)
303	for _, f := range filts {
304		if bad := matchfilterX(h, f); bad != "" {
305			if h.Precis == "" {
306				h.Precis = bad
307			}
308			h.Open = ""
309			break
310		}
311	}
312	filts = getfilters(userid, filtRewrite)
313	for _, f := range filts {
314		if matchfilter(h, f) {
315			h.Noise = f.re_rewrite.ReplaceAllString(h.Noise, f.Replace)
316		}
317	}
318}
319
320var untagged = cache.New(cache.Options{Filler: func(userid int64) (map[string]bool, bool) {
321	rows, err := stmtUntagged.Query(userid)
322	if err != nil {
323		log.Printf("error query untagged: %s", err)
324		return nil, false
325	}
326	defer rows.Close()
327	bad := make(map[string]bool)
328	for rows.Next() {
329		var xid, rid string
330		var flags int64
331		err = rows.Scan(&xid, &rid, &flags)
332		if err != nil {
333			log.Printf("error scanning untag: %s", err)
334			continue
335		}
336		if flags&flagIsUntagged != 0 {
337			bad[xid] = true
338		}
339		if bad[rid] {
340			bad[xid] = true
341		}
342	}
343	return bad, true
344}})
345
346func osmosis(honks []*Honk, userid int64, withfilt bool) []*Honk {
347	var badparents map[string]bool
348	untagged.GetAndLock(userid, &badparents)
349	j := 0
350	reversehonks(honks)
351	for _, h := range honks {
352		if badparents[h.RID] {
353			badparents[h.XID] = true
354			continue
355		}
356		honks[j] = h
357		j++
358	}
359	untagged.Unlock()
360	honks = honks[0:j]
361	reversehonks(honks)
362	if !withfilt {
363		return honks
364	}
365	filts := getfilters(userid, filtHide)
366	j = 0
367outer:
368	for _, h := range honks {
369		for _, f := range filts {
370			if matchfilter(h, f) {
371				continue outer
372			}
373		}
374		honks[j] = h
375		j++
376	}
377	honks = honks[0:j]
378	return honks
379}