all repos — honk @ c5b57ac802ba0131862fdab216c7b421989366bb

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.Actor == origin {
147			log.Printf("rejecting origin: %s", origin)
148			return true
149		}
150	}
151	return false
152}
153
154func rejectactor(userid int64, actor string) bool {
155	origin := originate(actor)
156	filts := getfilters(userid, filtReject)
157	for _, f := range filts {
158		if f.Actor == actor || (origin != "" && f.Actor == origin) {
159			log.Printf("rejecting actor: %s", actor)
160			return true
161		}
162	}
163	return false
164}
165
166func stealthmode(userid int64, r *http.Request) bool {
167	agent := r.UserAgent()
168	agent = originate(agent)
169	if agent != "" {
170		fake := rejectorigin(userid, agent)
171		if fake {
172			log.Printf("faking 404 for %s", agent)
173			return true
174		}
175	}
176	return false
177}
178
179func matchfilter(h *Honk, f *Filter) bool {
180	match := true
181	if match && f.Actor != "" {
182		match = false
183		if f.Actor == h.Honker || f.Actor == h.Oonker {
184			match = true
185		}
186		if !match && (f.Actor == originate(h.Honker) ||
187			f.Actor == originate(h.Oonker) ||
188			f.Actor == originate(h.XID)) {
189			match = true
190		}
191		if !match && f.IncludeAudience {
192			for _, a := range h.Audience {
193				if f.Actor == a || f.Actor == originate(a) {
194					match = true
195					break
196				}
197			}
198		}
199	}
200	if match && f.IsAnnounce {
201		match = false
202		if (f.AnnounceOf == "" && h.Oonker != "") || f.AnnounceOf == h.Oonker ||
203			f.AnnounceOf == originate(h.Oonker) {
204			match = true
205		}
206	}
207	if match && f.Text != "" {
208		match = false
209		re := f.re_text
210		if re.MatchString(h.Noise) || re.MatchString(h.Precis) {
211			match = true
212		}
213		if !match {
214			for _, d := range h.Donks {
215				if re.MatchString(d.Desc) {
216					match = true
217				}
218			}
219		}
220	}
221	return match
222}
223
224func rejectxonk(xonk *Honk) bool {
225	filts := getfilters(xonk.UserID, filtReject)
226	for _, f := range filts {
227		if matchfilter(xonk, f) {
228			log.Printf("rejecting %s because %s", xonk.XID, f.Actor)
229			return true
230		}
231	}
232	return false
233}
234
235func skipMedia(xonk *Honk) bool {
236	filts := getfilters(xonk.UserID, filtSkipMedia)
237	for _, f := range filts {
238		if matchfilter(xonk, f) {
239			return true
240		}
241	}
242	return false
243}
244
245func unsee(userid int64, h *Honk) {
246	filts := getfilters(userid, filtCollapse)
247	for _, f := range filts {
248		if matchfilter(h, f) {
249			bad := f.Text
250			if f.Actor != "" {
251				bad = f.Actor
252			}
253			if h.Precis == "" {
254				h.Precis = bad
255			}
256			h.Open = ""
257			break
258		}
259	}
260	filts = getfilters(userid, filtRewrite)
261	for _, f := range filts {
262		if matchfilter(h, f) {
263			h.Noise = f.re_rewrite.ReplaceAllString(h.Noise, f.Replace)
264		}
265	}
266}
267
268func osmosis(honks []*Honk, userid int64) []*Honk {
269	filts := getfilters(userid, filtHide)
270	j := 0
271outer:
272	for _, h := range honks {
273		for _, f := range filts {
274			if matchfilter(h, f) {
275				continue outer
276			}
277		}
278		honks[j] = h
279		j++
280	}
281	honks = honks[0:j]
282	return honks
283}