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)
23
24type Filter struct {
25 ID int64
26 Actor string
27 IncludeAudience bool
28 Text string
29 IsAnnounce bool
30 Reject bool
31 SkipMedia bool
32 Hide bool
33 Collapse bool
34 Rewrite string
35 re_rewrite *regexp.Regexp
36 Replace string
37}
38
39type filtType uint
40
41const (
42 filtNone filtType = iota
43 filtAny
44 filtReject
45 filtSkipMedia
46 filtHide
47 filtCollapse
48 filtRewrite
49)
50
51var filtNames = []string{"None", "Any", "Reject", "SkipMedia", "Hide", "Collapse", "Rewrite"}
52
53func (ft filtType) String() string {
54 return filtNames[ft]
55}
56
57type afiltermap map[filtType][]*Filter
58
59var filtcache = cacheNew(func(userid int64) (afiltermap, bool) {
60 rows, err := stmtGetFilters.Query(userid)
61 if err != nil {
62 log.Printf("error querying filters: %s", err)
63 return nil, false
64 }
65 defer rows.Close()
66
67 filtmap := make(afiltermap)
68 for rows.Next() {
69 filt := new(Filter)
70 var j string
71 var filterid int64
72 err = rows.Scan(&filterid, &j)
73 if err == nil {
74 err = unjsonify(j, filt)
75 }
76 if err != nil {
77 log.Printf("error scanning filter: %s", err)
78 continue
79 }
80 filt.ID = filterid
81 if filt.Reject {
82 filtmap[filtReject] = append(filtmap[filtReject], filt)
83 }
84 if filt.SkipMedia {
85 filtmap[filtSkipMedia] = append(filtmap[filtSkipMedia], filt)
86 }
87 if filt.Hide {
88 filtmap[filtHide] = append(filtmap[filtHide], filt)
89 }
90 if filt.Collapse {
91 filtmap[filtCollapse] = append(filtmap[filtCollapse], filt)
92 }
93 if filt.Rewrite != "" {
94 filtmap[filtRewrite] = append(filtmap[filtRewrite], filt)
95 }
96 }
97 return filtmap, true
98})
99
100func getfilters(userid int64, scope filtType) []*Filter {
101 var filtmap afiltermap
102 ok := filtcache.Get(userid, &filtmap)
103 if ok {
104 return filtmap[scope]
105 }
106 return nil
107}
108
109func rejectorigin(userid int64, origin string) bool {
110 if o := originate(origin); o != "" {
111 origin = o
112 }
113 filts := getfilters(userid, filtReject)
114 for _, f := range filts {
115 if f.Actor == origin {
116 log.Printf("rejecting origin: %s", origin)
117 return true
118 }
119 }
120 return false
121}
122
123func rejectactor(userid int64, actor string) bool {
124 origin := originate(actor)
125 filts := getfilters(userid, filtReject)
126 for _, f := range filts {
127 if f.Actor == actor || (origin != "" && f.Actor == origin) {
128 log.Printf("rejecting actor: %s", actor)
129 return true
130 }
131 }
132 return false
133}
134
135func stealthmode(userid int64, r *http.Request) bool {
136 agent := r.UserAgent()
137 agent = originate(agent)
138 if agent != "" {
139 fake := rejectorigin(userid, agent)
140 if fake {
141 log.Printf("faking 404 for %s", agent)
142 return fake
143 }
144 }
145 return false
146}
147
148// todo
149func matchfilter(h *Honk, filts []*Filter) bool {
150 origin := originate(h.XID)
151 for _, f := range filts {
152 if f.Actor == origin || f.Actor == h.Honker {
153 return true
154 }
155 if f.Text != "" {
156 for _, d := range h.Donks {
157 if d.Desc == f.Text {
158 return true
159 }
160 }
161 }
162 }
163 return false
164}
165
166func rejectnote(xonk *Honk) bool {
167 filts := getfilters(xonk.UserID, filtReject)
168 return matchfilter(xonk, filts)
169}
170
171func skipMedia(xonk *Honk) bool {
172 filts := getfilters(xonk.UserID, filtSkipMedia)
173 return matchfilter(xonk, filts)
174}
175
176// todo
177func unsee(filts []*Filter, h *Honk) string {
178 return ""
179}
180
181func osmosis(honks []*Honk, userid int64) []*Honk {
182 filts := getfilters(userid, filtHide)
183 j := 0
184outer:
185 for _, h := range honks {
186 if matchfilter(h, filts) {
187 continue outer
188 }
189 honks[j] = h
190 j++
191 }
192 honks = honks[0:j]
193 return honks
194}