2019-09-28 23:40:24 +02:00
|
|
|
//
|
|
|
|
// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
|
|
|
|
//
|
|
|
|
// Permission to use, copy, modify, and distribute this software for any
|
|
|
|
// purpose with or without fee is hereby granted, provided that the above
|
|
|
|
// copyright notice and this permission notice appear in all copies.
|
|
|
|
//
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"log"
|
2019-10-04 22:30:33 +02:00
|
|
|
"net/http"
|
|
|
|
"regexp"
|
2019-10-07 02:26:21 +02:00
|
|
|
"sort"
|
|
|
|
"time"
|
2019-10-13 01:25:23 +02:00
|
|
|
|
|
|
|
"humungus.tedunangst.com/r/webs/cache"
|
2019-09-28 23:40:24 +02:00
|
|
|
)
|
|
|
|
|
2019-10-04 22:30:33 +02:00
|
|
|
type Filter struct {
|
2019-10-07 02:26:21 +02:00
|
|
|
ID int64 `json:"-"`
|
|
|
|
Actions []filtType `json:"-"`
|
|
|
|
Name string
|
|
|
|
Date time.Time
|
|
|
|
Actor string `json:",omitempty"`
|
|
|
|
IncludeAudience bool `json:",omitempty"`
|
|
|
|
Text string `json:",omitempty"`
|
2019-10-05 03:57:53 +02:00
|
|
|
re_text *regexp.Regexp
|
2019-10-07 02:26:21 +02:00
|
|
|
IsAnnounce bool `json:",omitempty"`
|
|
|
|
AnnounceOf string `json:",omitempty"`
|
|
|
|
Reject bool `json:",omitempty"`
|
|
|
|
SkipMedia bool `json:",omitempty"`
|
|
|
|
Hide bool `json:",omitempty"`
|
|
|
|
Collapse bool `json:",omitempty"`
|
|
|
|
Rewrite string `json:",omitempty"`
|
2019-10-04 22:30:33 +02:00
|
|
|
re_rewrite *regexp.Regexp
|
2019-10-07 02:26:21 +02:00
|
|
|
Replace string `json:",omitempty"`
|
2019-10-21 08:28:35 +02:00
|
|
|
Expiration time.Time
|
2019-10-04 22:30:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type filtType uint
|
|
|
|
|
|
|
|
const (
|
|
|
|
filtNone filtType = iota
|
|
|
|
filtAny
|
|
|
|
filtReject
|
|
|
|
filtSkipMedia
|
|
|
|
filtHide
|
|
|
|
filtCollapse
|
|
|
|
filtRewrite
|
|
|
|
)
|
|
|
|
|
|
|
|
var filtNames = []string{"None", "Any", "Reject", "SkipMedia", "Hide", "Collapse", "Rewrite"}
|
|
|
|
|
|
|
|
func (ft filtType) String() string {
|
|
|
|
return filtNames[ft]
|
|
|
|
}
|
|
|
|
|
|
|
|
type afiltermap map[filtType][]*Filter
|
|
|
|
|
2019-10-21 08:28:35 +02:00
|
|
|
var filtcache *cache.Cache
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
// resolve init loop
|
|
|
|
filtcache = cache.New(cache.Options{Filler: filtcachefiller})
|
|
|
|
}
|
|
|
|
|
|
|
|
func filtcachefiller(userid int64) (afiltermap, bool) {
|
2019-10-04 22:30:33 +02:00
|
|
|
rows, err := stmtGetFilters.Query(userid)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("error querying filters: %s", err)
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
|
2019-10-21 08:28:35 +02:00
|
|
|
now := time.Now()
|
|
|
|
|
|
|
|
var expflush time.Time
|
|
|
|
|
2019-10-04 22:30:33 +02:00
|
|
|
filtmap := make(afiltermap)
|
|
|
|
for rows.Next() {
|
|
|
|
filt := new(Filter)
|
|
|
|
var j string
|
|
|
|
var filterid int64
|
|
|
|
err = rows.Scan(&filterid, &j)
|
|
|
|
if err == nil {
|
|
|
|
err = unjsonify(j, filt)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("error scanning filter: %s", err)
|
|
|
|
continue
|
|
|
|
}
|
2019-10-21 08:28:35 +02:00
|
|
|
if !filt.Expiration.IsZero() {
|
|
|
|
if filt.Expiration.Before(now) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if expflush.IsZero() || filt.Expiration.Before(expflush) {
|
|
|
|
expflush = filt.Expiration
|
|
|
|
}
|
|
|
|
}
|
2019-10-05 03:57:53 +02:00
|
|
|
if filt.Text != "" {
|
|
|
|
filt.re_text, err = regexp.Compile("\\b(?i:" + filt.Text + ")\\b")
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("error compiling filter text: %s", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
2019-10-07 02:26:21 +02:00
|
|
|
if filt.Rewrite != "" {
|
|
|
|
filt.re_rewrite, err = regexp.Compile("\\b(?i:" + filt.Rewrite + ")\\b")
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("error compiling filter rewrite: %s", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
2019-10-04 22:30:33 +02:00
|
|
|
filt.ID = filterid
|
|
|
|
if filt.Reject {
|
2019-10-07 02:26:21 +02:00
|
|
|
filt.Actions = append(filt.Actions, filtReject)
|
2019-10-04 22:30:33 +02:00
|
|
|
filtmap[filtReject] = append(filtmap[filtReject], filt)
|
|
|
|
}
|
|
|
|
if filt.SkipMedia {
|
2019-10-07 02:26:21 +02:00
|
|
|
filt.Actions = append(filt.Actions, filtSkipMedia)
|
2019-10-04 22:30:33 +02:00
|
|
|
filtmap[filtSkipMedia] = append(filtmap[filtSkipMedia], filt)
|
|
|
|
}
|
|
|
|
if filt.Hide {
|
2019-10-07 02:26:21 +02:00
|
|
|
filt.Actions = append(filt.Actions, filtHide)
|
2019-10-04 22:30:33 +02:00
|
|
|
filtmap[filtHide] = append(filtmap[filtHide], filt)
|
|
|
|
}
|
|
|
|
if filt.Collapse {
|
2019-10-07 02:26:21 +02:00
|
|
|
filt.Actions = append(filt.Actions, filtCollapse)
|
2019-10-04 22:30:33 +02:00
|
|
|
filtmap[filtCollapse] = append(filtmap[filtCollapse], filt)
|
|
|
|
}
|
|
|
|
if filt.Rewrite != "" {
|
2019-10-07 02:26:21 +02:00
|
|
|
filt.Actions = append(filt.Actions, filtRewrite)
|
2019-10-04 22:30:33 +02:00
|
|
|
filtmap[filtRewrite] = append(filtmap[filtRewrite], filt)
|
|
|
|
}
|
2019-10-07 02:26:21 +02:00
|
|
|
filtmap[filtAny] = append(filtmap[filtAny], filt)
|
2019-10-04 22:30:33 +02:00
|
|
|
}
|
2019-10-07 02:26:21 +02:00
|
|
|
sorting := filtmap[filtAny]
|
|
|
|
sort.Slice(filtmap[filtAny], func(i, j int) bool {
|
|
|
|
return sorting[i].Name < sorting[j].Name
|
|
|
|
})
|
2019-10-21 08:28:35 +02:00
|
|
|
if !expflush.IsZero() {
|
|
|
|
dur := expflush.Sub(now)
|
|
|
|
go filtcacheclear(userid, dur)
|
|
|
|
}
|
2019-10-04 22:30:33 +02:00
|
|
|
return filtmap, true
|
2019-10-21 08:28:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func filtcacheclear(userid int64, dur time.Duration) {
|
|
|
|
time.Sleep(dur + time.Second)
|
|
|
|
filtcache.Clear(userid)
|
|
|
|
}
|
2019-10-04 22:30:33 +02:00
|
|
|
|
|
|
|
func getfilters(userid int64, scope filtType) []*Filter {
|
|
|
|
var filtmap afiltermap
|
|
|
|
ok := filtcache.Get(userid, &filtmap)
|
|
|
|
if ok {
|
|
|
|
return filtmap[scope]
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2019-09-28 23:40:24 +02:00
|
|
|
|
2019-10-04 22:30:33 +02:00
|
|
|
func rejectorigin(userid int64, origin string) bool {
|
|
|
|
if o := originate(origin); o != "" {
|
|
|
|
origin = o
|
|
|
|
}
|
|
|
|
filts := getfilters(userid, filtReject)
|
|
|
|
for _, f := range filts {
|
2019-10-08 21:00:15 +02:00
|
|
|
if f.IsAnnounce || f.Text != "" {
|
|
|
|
continue
|
|
|
|
}
|
2019-10-04 22:30:33 +02:00
|
|
|
if f.Actor == origin {
|
|
|
|
log.Printf("rejecting origin: %s", origin)
|
|
|
|
return true
|
|
|
|
}
|
2019-09-28 23:40:24 +02:00
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2019-10-04 22:30:33 +02:00
|
|
|
|
|
|
|
func rejectactor(userid int64, actor string) bool {
|
|
|
|
origin := originate(actor)
|
|
|
|
filts := getfilters(userid, filtReject)
|
|
|
|
for _, f := range filts {
|
2019-10-08 21:00:15 +02:00
|
|
|
if f.IsAnnounce || f.Text != "" {
|
|
|
|
continue
|
|
|
|
}
|
2019-10-04 22:30:33 +02:00
|
|
|
if f.Actor == actor || (origin != "" && f.Actor == origin) {
|
|
|
|
log.Printf("rejecting actor: %s", actor)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func stealthmode(userid int64, r *http.Request) bool {
|
|
|
|
agent := r.UserAgent()
|
|
|
|
agent = originate(agent)
|
|
|
|
if agent != "" {
|
|
|
|
fake := rejectorigin(userid, agent)
|
|
|
|
if fake {
|
|
|
|
log.Printf("faking 404 for %s", agent)
|
2019-10-07 02:26:21 +02:00
|
|
|
return true
|
2019-10-04 22:30:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-10-05 03:47:27 +02:00
|
|
|
func matchfilter(h *Honk, f *Filter) bool {
|
|
|
|
match := true
|
|
|
|
if match && f.Actor != "" {
|
|
|
|
match = false
|
|
|
|
if f.Actor == h.Honker || f.Actor == h.Oonker {
|
|
|
|
match = true
|
|
|
|
}
|
|
|
|
if !match && (f.Actor == originate(h.Honker) ||
|
|
|
|
f.Actor == originate(h.Oonker) ||
|
|
|
|
f.Actor == originate(h.XID)) {
|
|
|
|
match = true
|
2019-10-04 22:30:33 +02:00
|
|
|
}
|
2019-10-05 03:47:27 +02:00
|
|
|
if !match && f.IncludeAudience {
|
|
|
|
for _, a := range h.Audience {
|
|
|
|
if f.Actor == a || f.Actor == originate(a) {
|
|
|
|
match = true
|
|
|
|
break
|
2019-10-04 22:30:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-10-07 02:26:21 +02:00
|
|
|
if match && f.IsAnnounce {
|
|
|
|
match = false
|
|
|
|
if (f.AnnounceOf == "" && h.Oonker != "") || f.AnnounceOf == h.Oonker ||
|
|
|
|
f.AnnounceOf == originate(h.Oonker) {
|
|
|
|
match = true
|
|
|
|
}
|
|
|
|
}
|
2019-10-05 03:47:27 +02:00
|
|
|
if match && f.Text != "" {
|
|
|
|
match = false
|
2019-10-05 03:57:53 +02:00
|
|
|
re := f.re_text
|
|
|
|
if re.MatchString(h.Noise) || re.MatchString(h.Precis) {
|
|
|
|
match = true
|
|
|
|
}
|
|
|
|
if !match {
|
|
|
|
for _, d := range h.Donks {
|
|
|
|
if re.MatchString(d.Desc) {
|
|
|
|
match = true
|
|
|
|
}
|
2019-10-05 03:47:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-10-07 02:26:21 +02:00
|
|
|
return match
|
2019-10-04 22:30:33 +02:00
|
|
|
}
|
|
|
|
|
2019-10-05 03:47:27 +02:00
|
|
|
func rejectxonk(xonk *Honk) bool {
|
2019-10-04 22:30:33 +02:00
|
|
|
filts := getfilters(xonk.UserID, filtReject)
|
2019-10-05 03:47:27 +02:00
|
|
|
for _, f := range filts {
|
2019-10-05 03:49:49 +02:00
|
|
|
if matchfilter(xonk, f) {
|
2019-10-07 04:48:23 +02:00
|
|
|
log.Printf("rejecting %s because %s", xonk.XID, f.Actor)
|
2019-10-05 03:47:27 +02:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
2019-10-04 22:30:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func skipMedia(xonk *Honk) bool {
|
|
|
|
filts := getfilters(xonk.UserID, filtSkipMedia)
|
2019-10-05 03:47:27 +02:00
|
|
|
for _, f := range filts {
|
2019-10-05 03:49:49 +02:00
|
|
|
if matchfilter(xonk, f) {
|
2019-10-05 03:47:27 +02:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
2019-10-04 22:30:33 +02:00
|
|
|
}
|
|
|
|
|
2019-10-07 02:26:21 +02:00
|
|
|
func unsee(userid int64, h *Honk) {
|
2019-10-05 03:57:53 +02:00
|
|
|
filts := getfilters(userid, filtCollapse)
|
|
|
|
for _, f := range filts {
|
|
|
|
if matchfilter(h, f) {
|
2019-10-07 02:26:21 +02:00
|
|
|
bad := f.Text
|
|
|
|
if f.Actor != "" {
|
|
|
|
bad = f.Actor
|
|
|
|
}
|
|
|
|
if h.Precis == "" {
|
|
|
|
h.Precis = bad
|
|
|
|
}
|
|
|
|
h.Open = ""
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
filts = getfilters(userid, filtRewrite)
|
|
|
|
for _, f := range filts {
|
|
|
|
if matchfilter(h, f) {
|
|
|
|
h.Noise = f.re_rewrite.ReplaceAllString(h.Noise, f.Replace)
|
2019-10-05 03:57:53 +02:00
|
|
|
}
|
|
|
|
}
|
2019-10-04 22:30:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func osmosis(honks []*Honk, userid int64) []*Honk {
|
|
|
|
filts := getfilters(userid, filtHide)
|
|
|
|
j := 0
|
|
|
|
outer:
|
|
|
|
for _, h := range honks {
|
2019-10-05 03:49:49 +02:00
|
|
|
for _, f := range filts {
|
|
|
|
if matchfilter(h, f) {
|
|
|
|
continue outer
|
|
|
|
}
|
2019-10-04 22:30:33 +02:00
|
|
|
}
|
|
|
|
honks[j] = h
|
|
|
|
j++
|
|
|
|
}
|
|
|
|
honks = honks[0:j]
|
|
|
|
return honks
|
|
|
|
}
|