all repos — honk @ c71060dae106572af3f352a1273a0ade9588b554

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
 26type Filter struct {
 27	ID              int64      `json:"-"`
 28	Actions         []filtType `json:"-"`
 29	Name            string
 30	Date            time.Time
 31	Actor           string `json:",omitempty"`
 32	IncludeAudience bool   `json:",omitempty"`
 33	Text            string `json:",omitempty"`
 34	re_text         *regexp.Regexp
 35	IsAnnounce      bool   `json:",omitempty"`
 36	AnnounceOf      string `json:",omitempty"`
 37	Reject          bool   `json:",omitempty"`
 38	SkipMedia       bool   `json:",omitempty"`
 39	Hide            bool   `json:",omitempty"`
 40	Collapse        bool   `json:",omitempty"`
 41	Rewrite         string `json:",omitempty"`
 42	re_rewrite      *regexp.Regexp
 43	Replace         string `json:",omitempty"`
 44}
 45
 46type filtType uint
 47
 48const (
 49	filtNone filtType = iota
 50	filtAny
 51	filtReject
 52	filtSkipMedia
 53	filtHide
 54	filtCollapse
 55	filtRewrite
 56)
 57
 58var filtNames = []string{"None", "Any", "Reject", "SkipMedia", "Hide", "Collapse", "Rewrite"}
 59
 60func (ft filtType) String() string {
 61	return filtNames[ft]
 62}
 63
 64type afiltermap map[filtType][]*Filter
 65
 66var filtcache = cacheNew(cacheOptions{Filler: func(userid int64) (afiltermap, bool) {
 67	rows, err := stmtGetFilters.Query(userid)
 68	if err != nil {
 69		log.Printf("error querying filters: %s", err)
 70		return nil, false
 71	}
 72	defer rows.Close()
 73
 74	filtmap := make(afiltermap)
 75	for rows.Next() {
 76		filt := new(Filter)
 77		var j string
 78		var filterid int64
 79		err = rows.Scan(&filterid, &j)
 80		if err == nil {
 81			err = unjsonify(j, filt)
 82		}
 83		if err != nil {
 84			log.Printf("error scanning filter: %s", err)
 85			continue
 86		}
 87		if filt.Text != "" {
 88			filt.re_text, err = regexp.Compile("\\b(?i:" + filt.Text + ")\\b")
 89			if err != nil {
 90				log.Printf("error compiling filter text: %s", err)
 91				continue
 92			}
 93		}
 94		if filt.Rewrite != "" {
 95			filt.re_rewrite, err = regexp.Compile("\\b(?i:" + filt.Rewrite + ")\\b")
 96			if err != nil {
 97				log.Printf("error compiling filter rewrite: %s", err)
 98				continue
 99			}
100		}
101		filt.ID = filterid
102		if filt.Reject {
103			filt.Actions = append(filt.Actions, filtReject)
104			filtmap[filtReject] = append(filtmap[filtReject], filt)
105		}
106		if filt.SkipMedia {
107			filt.Actions = append(filt.Actions, filtSkipMedia)
108			filtmap[filtSkipMedia] = append(filtmap[filtSkipMedia], filt)
109		}
110		if filt.Hide {
111			filt.Actions = append(filt.Actions, filtHide)
112			filtmap[filtHide] = append(filtmap[filtHide], filt)
113		}
114		if filt.Collapse {
115			filt.Actions = append(filt.Actions, filtCollapse)
116			filtmap[filtCollapse] = append(filtmap[filtCollapse], filt)
117		}
118		if filt.Rewrite != "" {
119			filt.Actions = append(filt.Actions, filtRewrite)
120			filtmap[filtRewrite] = append(filtmap[filtRewrite], filt)
121		}
122		filtmap[filtAny] = append(filtmap[filtAny], filt)
123	}
124	sorting := filtmap[filtAny]
125	sort.Slice(filtmap[filtAny], func(i, j int) bool {
126		return sorting[i].Name < sorting[j].Name
127	})
128	return filtmap, true
129}})
130
131func getfilters(userid int64, scope filtType) []*Filter {
132	var filtmap afiltermap
133	ok := filtcache.Get(userid, &filtmap)
134	if ok {
135		return filtmap[scope]
136	}
137	return nil
138}
139
140func rejectorigin(userid int64, origin string) bool {
141	if o := originate(origin); o != "" {
142		origin = o
143	}
144	filts := getfilters(userid, filtReject)
145	for _, f := range filts {
146		if f.IsAnnounce || f.Text != "" {
147			continue
148		}
149		if f.Actor == origin {
150			log.Printf("rejecting origin: %s", origin)
151			return true
152		}
153	}
154	return false
155}
156
157func rejectactor(userid int64, actor string) bool {
158	origin := originate(actor)
159	filts := getfilters(userid, filtReject)
160	for _, f := range filts {
161		if f.IsAnnounce || f.Text != "" {
162			continue
163		}
164		if f.Actor == actor || (origin != "" && f.Actor == origin) {
165			log.Printf("rejecting actor: %s", actor)
166			return true
167		}
168	}
169	return false
170}
171
172func stealthmode(userid int64, r *http.Request) bool {
173	agent := r.UserAgent()
174	agent = originate(agent)
175	if agent != "" {
176		fake := rejectorigin(userid, agent)
177		if fake {
178			log.Printf("faking 404 for %s", agent)
179			return true
180		}
181	}
182	return false
183}
184
185func matchfilter(h *Honk, f *Filter) bool {
186	match := true
187	if match && f.Actor != "" {
188		match = false
189		if f.Actor == h.Honker || f.Actor == h.Oonker {
190			match = true
191		}
192		if !match && (f.Actor == originate(h.Honker) ||
193			f.Actor == originate(h.Oonker) ||
194			f.Actor == originate(h.XID)) {
195			match = true
196		}
197		if !match && f.IncludeAudience {
198			for _, a := range h.Audience {
199				if f.Actor == a || f.Actor == originate(a) {
200					match = true
201					break
202				}
203			}
204		}
205	}
206	if match && f.IsAnnounce {
207		match = false
208		if (f.AnnounceOf == "" && h.Oonker != "") || f.AnnounceOf == h.Oonker ||
209			f.AnnounceOf == originate(h.Oonker) {
210			match = true
211		}
212	}
213	if match && f.Text != "" {
214		match = false
215		re := f.re_text
216		if re.MatchString(h.Noise) || re.MatchString(h.Precis) {
217			match = true
218		}
219		if !match {
220			for _, d := range h.Donks {
221				if re.MatchString(d.Desc) {
222					match = true
223				}
224			}
225		}
226	}
227	return match
228}
229
230func rejectxonk(xonk *Honk) bool {
231	filts := getfilters(xonk.UserID, filtReject)
232	for _, f := range filts {
233		if matchfilter(xonk, f) {
234			log.Printf("rejecting %s because %s", xonk.XID, f.Actor)
235			return true
236		}
237	}
238	return false
239}
240
241func skipMedia(xonk *Honk) bool {
242	filts := getfilters(xonk.UserID, filtSkipMedia)
243	for _, f := range filts {
244		if matchfilter(xonk, f) {
245			return true
246		}
247	}
248	return false
249}
250
251func unsee(userid int64, h *Honk) {
252	filts := getfilters(userid, filtCollapse)
253	for _, f := range filts {
254		if matchfilter(h, f) {
255			bad := f.Text
256			if f.Actor != "" {
257				bad = f.Actor
258			}
259			if h.Precis == "" {
260				h.Precis = bad
261			}
262			h.Open = ""
263			break
264		}
265	}
266	filts = getfilters(userid, filtRewrite)
267	for _, f := range filts {
268		if matchfilter(h, f) {
269			h.Noise = f.re_rewrite.ReplaceAllString(h.Noise, f.Replace)
270		}
271	}
272}
273
274func osmosis(honks []*Honk, userid int64) []*Honk {
275	filts := getfilters(userid, filtHide)
276	j := 0
277outer:
278	for _, h := range honks {
279		for _, f := range filts {
280			if matchfilter(h, f) {
281				continue outer
282			}
283		}
284		honks[j] = h
285		j++
286	}
287	honks = honks[0:j]
288	return honks
289}