all repos — honk @ b870462dc7d91904f2966e226bec9e0ca641a2a9

my fork of honk

fun.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	"crypto/rand"
 20	"crypto/rsa"
 21	"fmt"
 22	"html"
 23	"html/template"
 24	"log"
 25	"net/http"
 26	"os"
 27	"regexp"
 28	"strings"
 29	"sync"
 30
 31	"humungus.tedunangst.com/r/webs/htfilter"
 32)
 33
 34func reverbolate(userid int64, honks []*Honk) {
 35	filt := htfilter.New()
 36	zilences := getzilences(userid)
 37	for _, h := range honks {
 38		h.What += "ed"
 39		if h.What == "tonked" {
 40			h.What = "honked back"
 41			h.Style = "subtle"
 42		}
 43		if !h.Public {
 44			h.Style = "limited"
 45		}
 46		if h.Whofore == 2 || h.Whofore == 3 {
 47			h.URL = h.XID
 48			h.Noise = mentionize(h.Noise)
 49			h.Username, h.Handle = honkerhandle(h.Honker)
 50		} else {
 51			_, h.Handle = honkerhandle(h.Honker)
 52			h.Username = h.Handle
 53			if len(h.Username) > 20 {
 54				h.Username = h.Username[:20] + ".."
 55			}
 56			if h.URL == "" {
 57				h.URL = h.XID
 58			}
 59		}
 60		if h.Oonker != "" {
 61			_, h.Oondle = honkerhandle(h.Oonker)
 62		}
 63		zap := make(map[*Donk]bool)
 64		h.Noise = unpucker(h.Noise)
 65		h.Open = "open"
 66		if badword := unsee(zilences, h.Precis, h.Noise); badword != "" {
 67			if h.Precis == "" {
 68				h.Precis = badword
 69			}
 70			h.Open = ""
 71		} else if h.Precis == "unspecified horror" {
 72			h.Precis = ""
 73		}
 74		h.HTML, _ = filt.String(h.Noise)
 75		emuxifier := func(e string) string {
 76			for _, d := range h.Donks {
 77				if d.Name == e {
 78					zap[d] = true
 79					if d.Local {
 80						return fmt.Sprintf(`<img class="emu" title="%s" src="/d/%s">`, d.Name, d.XID)
 81					}
 82				}
 83			}
 84			return e
 85		}
 86		h.HTML = template.HTML(re_emus.ReplaceAllStringFunc(string(h.HTML), emuxifier))
 87		j := 0
 88		for i := 0; i < len(h.Donks); i++ {
 89			if !zap[h.Donks[i]] {
 90				h.Donks[j] = h.Donks[i]
 91				j++
 92			}
 93		}
 94		h.Donks = h.Donks[:j]
 95	}
 96}
 97
 98func unsee(zilences []*regexp.Regexp, precis string, noise string) string {
 99	for _, z := range zilences {
100		if z.MatchString(precis) || z.MatchString(noise) {
101			if precis == "" {
102				w := z.String()
103				return w[6 : len(w)-3]
104			}
105			return precis
106		}
107	}
108	return ""
109}
110
111func osmosis(honks []*Honk, userid int64) []*Honk {
112	zords := getzords(userid)
113	for i, j := 0, len(honks)-1; i < j; i, j = i+1, j-1 {
114		honks[i], honks[j] = honks[j], honks[i]
115	}
116
117	j := 0
118	filtered := make(map[string]bool)
119outer:
120	for _, h := range honks {
121		if h.RID != "" && filtered[h.RID] {
122			filtered[h.XID] = true
123			continue outer
124		}
125
126		for _, z := range zords {
127			if z.MatchString(h.Precis) || z.MatchString(h.Noise) {
128				filtered[h.XID] = true
129				continue outer
130			}
131		}
132		honks[j] = h
133		j++
134	}
135	honks = honks[0:j]
136	for i, j := 0, len(honks)-1; i < j; i, j = i+1, j-1 {
137		honks[i], honks[j] = honks[j], honks[i]
138	}
139	return honks
140}
141
142func shortxid(xid string) string {
143	idx := strings.LastIndexByte(xid, '/')
144	if idx == -1 {
145		return xid
146	}
147	return xid[idx+1:]
148}
149
150func xfiltrate() string {
151	letters := "BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz1234567891234567891234"
152	var b [18]byte
153	rand.Read(b[:])
154	for i, c := range b {
155		b[i] = letters[c&63]
156	}
157	s := string(b[:])
158	return s
159}
160
161var re_hashes = regexp.MustCompile(`(?:^|\W)#[[:alnum:]]+`)
162
163func ontologies(s string) []string {
164	m := re_hashes.FindAllString(s, -1)
165	j := 0
166	for _, h := range m {
167		if h[0] == '&' {
168			continue
169		}
170		if h[0] != '#' {
171			h = h[1:]
172		}
173		m[j] = h
174		j++
175	}
176	return m[:j]
177}
178
179type Mention struct {
180	who   string
181	where string
182}
183
184var re_mentions = regexp.MustCompile(`@[[:alnum:]._-]+@[[:alnum:].-]*[[:alnum:]]`)
185var re_urltions = regexp.MustCompile(`@https://\S+`)
186
187func grapevine(s string) []string {
188	var mentions []string
189	m := re_mentions.FindAllString(s, -1)
190	for i := range m {
191		where := gofish(m[i])
192		if where != "" {
193			mentions = append(mentions, where)
194		}
195	}
196	m = re_urltions.FindAllString(s, -1)
197	for i := range m {
198		mentions = append(mentions, m[i][1:])
199	}
200	return mentions
201}
202
203func bunchofgrapes(s string) []Mention {
204	m := re_mentions.FindAllString(s, -1)
205	var mentions []Mention
206	for i := range m {
207		where := gofish(m[i])
208		if where != "" {
209			mentions = append(mentions, Mention{who: m[i], where: where})
210		}
211	}
212	m = re_urltions.FindAllString(s, -1)
213	for i := range m {
214		mentions = append(mentions, Mention{who: m[i][1:], where: m[i][1:]})
215	}
216	return mentions
217}
218
219type Emu struct {
220	ID   string
221	Name string
222}
223
224var re_link = regexp.MustCompile(`@?https?://[^\s"]+[\w/)]`)
225var re_emus = regexp.MustCompile(`:[[:alnum:]_-]+:`)
226
227func herdofemus(noise string) []Emu {
228	m := re_emus.FindAllString(noise, -1)
229	m = oneofakind(m)
230	var emus []Emu
231	for _, e := range m {
232		fname := e[1 : len(e)-1]
233		_, err := os.Stat("emus/" + fname + ".png")
234		if err != nil {
235			continue
236		}
237		url := fmt.Sprintf("https://%s/emu/%s.png", serverName, fname)
238		emus = append(emus, Emu{ID: url, Name: e})
239	}
240	return emus
241}
242
243var re_memes = regexp.MustCompile("meme: ?([[:alnum:]_.-]+)")
244
245func memetize(honk *Honk) {
246	repl := func(x string) string {
247		name := x[5:]
248		if name[0] == ' ' {
249			name = name[1:]
250		}
251		fd, err := os.Open("memes/" + name)
252		if err != nil {
253			log.Printf("no meme for %s", name)
254			return x
255		}
256		var peek [512]byte
257		n, _ := fd.Read(peek[:])
258		ct := http.DetectContentType(peek[:n])
259		fd.Close()
260
261		url := fmt.Sprintf("https://%s/meme/%s", serverName, name)
262		res, err := stmtSaveFile.Exec("", name, url, ct, 0, "")
263		if err != nil {
264			log.Printf("error saving meme: %s", err)
265			return x
266		}
267		var d Donk
268		d.FileID, _ = res.LastInsertId()
269		d.XID = ""
270		d.Name = name
271		d.Media = ct
272		d.URL = url
273		d.Local = false
274		honk.Donks = append(honk.Donks, &d)
275		log.Printf("replace with -")
276		return ""
277	}
278	honk.Noise = re_memes.ReplaceAllStringFunc(honk.Noise, repl)
279}
280
281var re_bolder = regexp.MustCompile(`(^|\W)\*\*([\w\s,.!?'-]+)\*\*($|\W)`)
282var re_italicer = regexp.MustCompile(`(^|\W)\*([\w\s,.!?'-]+)\*($|\W)`)
283var re_bigcoder = regexp.MustCompile("```\n?((?s:.*?))\n?```\n?")
284var re_coder = regexp.MustCompile("`([^`]*)`")
285var re_quoter = regexp.MustCompile(`(?m:^&gt; (.*)\n?)`)
286
287func markitzero(s string) string {
288	var bigcodes []string
289	bigsaver := func(code string) string {
290		bigcodes = append(bigcodes, code)
291		return "``````"
292	}
293	s = re_bigcoder.ReplaceAllStringFunc(s, bigsaver)
294	var lilcodes []string
295	lilsaver := func(code string) string {
296		lilcodes = append(lilcodes, code)
297		return "`x`"
298	}
299	s = re_coder.ReplaceAllStringFunc(s, lilsaver)
300	s = re_bolder.ReplaceAllString(s, "$1<b>$2</b>$3")
301	s = re_italicer.ReplaceAllString(s, "$1<i>$2</i>$3")
302	s = re_quoter.ReplaceAllString(s, "<blockquote>$1</blockquote><p>")
303	lilun := func(s string) string {
304		code := lilcodes[0]
305		lilcodes = lilcodes[1:]
306		return code
307	}
308	s = re_coder.ReplaceAllStringFunc(s, lilun)
309	bigun := func(s string) string {
310		code := bigcodes[0]
311		bigcodes = bigcodes[1:]
312		return code
313	}
314	s = re_bigcoder.ReplaceAllStringFunc(s, bigun)
315	s = re_bigcoder.ReplaceAllString(s, "<pre><code>$1</code></pre><p>")
316	s = re_coder.ReplaceAllString(s, "<code>$1</code>")
317	return s
318}
319
320func obfusbreak(s string) string {
321	s = strings.TrimSpace(s)
322	s = strings.Replace(s, "\r", "", -1)
323	s = html.EscapeString(s)
324	// dammit go
325	s = strings.Replace(s, "&#39;", "'", -1)
326	linkfn := func(url string) string {
327		if url[0] == '@' {
328			return url
329		}
330		addparen := false
331		adddot := false
332		if strings.HasSuffix(url, ")") && strings.IndexByte(url, '(') == -1 {
333			url = url[:len(url)-1]
334			addparen = true
335		}
336		if strings.HasSuffix(url, ".") {
337			url = url[:len(url)-1]
338			adddot = true
339		}
340		url = fmt.Sprintf(`<a class="mention" href="%s">%s</a>`, url, url)
341		if adddot {
342			url += "."
343		}
344		if addparen {
345			url += ")"
346		}
347		return url
348	}
349	s = re_link.ReplaceAllStringFunc(s, linkfn)
350
351	s = markitzero(s)
352
353	s = strings.Replace(s, "\n", "<br>", -1)
354	return s
355}
356
357func mentionize(s string) string {
358	s = re_mentions.ReplaceAllStringFunc(s, func(m string) string {
359		where := gofish(m)
360		if where == "" {
361			return m
362		}
363		who := m[0 : 1+strings.IndexByte(m[1:], '@')]
364		return fmt.Sprintf(`<span class="h-card"><a class="u-url mention" href="%s">%s</a></span>`,
365			html.EscapeString(where), html.EscapeString(who))
366	})
367	s = re_urltions.ReplaceAllStringFunc(s, func(m string) string {
368		return fmt.Sprintf(`<span class="h-card"><a class="u-url mention" href="%s">%s</a></span>`,
369			html.EscapeString(m[1:]), html.EscapeString(m))
370	})
371	return s
372}
373
374var re_unurl = regexp.MustCompile("https://([^/]+).*/([^/]+)")
375var re_urlhost = regexp.MustCompile("https://([^/]+)")
376
377func originate(u string) string {
378	m := re_urlhost.FindStringSubmatch(u)
379	if len(m) > 1 {
380		return m[1]
381	}
382	return ""
383}
384
385func honkerhandle(h string) (string, string) {
386	m := re_unurl.FindStringSubmatch(h)
387	if len(m) > 2 {
388		return m[2], fmt.Sprintf("%s@%s", m[2], m[1])
389	}
390	return h, h
391}
392
393func prepend(s string, x []string) []string {
394	return append([]string{s}, x...)
395}
396
397// pleroma leaks followers addressed posts to followers
398func butnottooloud(aud []string) {
399	for i, a := range aud {
400		if strings.HasSuffix(a, "/followers") {
401			aud[i] = ""
402		}
403	}
404}
405
406func keepitquiet(aud []string) bool {
407	for _, a := range aud {
408		if a == thewholeworld {
409			return false
410		}
411	}
412	return true
413}
414
415func oneofakind(a []string) []string {
416	var x []string
417	for n, s := range a {
418		if s != "" {
419			x = append(x, s)
420			for i := n + 1; i < len(a); i++ {
421				if a[i] == s {
422					a[i] = ""
423				}
424			}
425		}
426	}
427	return x
428}
429
430var ziggies = make(map[string]*rsa.PrivateKey)
431var zaggies = make(map[string]*rsa.PublicKey)
432var ziggylock sync.Mutex
433
434func ziggy(username string) (keyname string, key *rsa.PrivateKey) {
435	ziggylock.Lock()
436	key = ziggies[username]
437	ziggylock.Unlock()
438	if key == nil {
439		db := opendatabase()
440		row := db.QueryRow("select seckey from users where username = ?", username)
441		var data string
442		row.Scan(&data)
443		var err error
444		key, _, err = pez(data)
445		if err != nil {
446			log.Printf("error decoding %s seckey: %s", username, err)
447			return
448		}
449		ziggylock.Lock()
450		ziggies[username] = key
451		ziggylock.Unlock()
452	}
453	keyname = fmt.Sprintf("https://%s/u/%s#key", serverName, username)
454	return
455}
456
457func zaggy(keyname string) (key *rsa.PublicKey) {
458	ziggylock.Lock()
459	key = zaggies[keyname]
460	ziggylock.Unlock()
461	if key != nil {
462		return
463	}
464	row := stmtGetXonker.QueryRow(keyname, "pubkey")
465	var data string
466	err := row.Scan(&data)
467	if err != nil {
468		log.Printf("hitting the webs for missing pubkey: %s", keyname)
469		j, err := GetJunk(keyname)
470		if err != nil {
471			log.Printf("error getting %s pubkey: %s", keyname, err)
472			return
473		}
474		var ok bool
475		data, ok = j.FindString([]string{"publicKey", "publicKeyPem"})
476		if !ok {
477			log.Printf("error finding %s pubkey", keyname)
478			return
479		}
480		_, ok = j.FindString([]string{"publicKey", "owner"})
481		if !ok {
482			log.Printf("error finding %s pubkey owner", keyname)
483			return
484		}
485		_, key, err = pez(data)
486		if err != nil {
487			log.Printf("error decoding %s pubkey: %s", keyname, err)
488			return
489		}
490		_, err = stmtSaveXonker.Exec(keyname, data, "pubkey")
491		if err != nil {
492			log.Printf("error saving key: %s", err)
493		}
494	} else {
495		_, key, err = pez(data)
496		if err != nil {
497			log.Printf("error decoding %s pubkey: %s", keyname, err)
498			return
499		}
500	}
501	ziggylock.Lock()
502	zaggies[keyname] = key
503	ziggylock.Unlock()
504	return
505}
506
507func makeitworksomehowwithoutregardforkeycontinuity(keyname string, r *http.Request, payload []byte) (string, error) {
508	_, err := stmtDeleteXonker.Exec(keyname, "pubkey")
509	if err != nil {
510		log.Printf("error deleting key: %s", err)
511	}
512	ziggylock.Lock()
513	delete(zaggies, keyname)
514	ziggylock.Unlock()
515	return zag(r, payload)
516}
517
518var thumbbiters map[int64]map[string]bool
519var zordses map[int64][]*regexp.Regexp
520var zilences map[int64][]*regexp.Regexp
521var thumblock sync.Mutex
522
523func bitethethumbs() {
524	rows, err := stmtThumbBiters.Query()
525	if err != nil {
526		log.Printf("error getting thumbbiters: %s", err)
527		return
528	}
529	defer rows.Close()
530
531	thumblock.Lock()
532	defer thumblock.Unlock()
533	thumbbiters = make(map[int64]map[string]bool)
534	zordses = make(map[int64][]*regexp.Regexp)
535	zilences = make(map[int64][]*regexp.Regexp)
536	for rows.Next() {
537		var userid int64
538		var name, wherefore string
539		err = rows.Scan(&userid, &name, &wherefore)
540		if err != nil {
541			log.Printf("error scanning zonker: %s", err)
542			continue
543		}
544		if wherefore == "zord" || wherefore == "zilence" {
545			zord := "\\b(?i:" + name + ")\\b"
546			re, err := regexp.Compile(zord)
547			if err != nil {
548				log.Printf("error compiling zord: %s", err)
549			} else {
550				if wherefore == "zord" {
551					zordses[userid] = append(zordses[userid], re)
552				} else {
553					zilences[userid] = append(zilences[userid], re)
554				}
555			}
556			continue
557		}
558		m := thumbbiters[userid]
559		if m == nil {
560			m = make(map[string]bool)
561			thumbbiters[userid] = m
562		}
563		m[name] = true
564	}
565}
566
567func getzords(userid int64) []*regexp.Regexp {
568	thumblock.Lock()
569	defer thumblock.Unlock()
570	return zordses[userid]
571}
572
573func getzilences(userid int64) []*regexp.Regexp {
574	thumblock.Lock()
575	defer thumblock.Unlock()
576	return zilences[userid]
577}
578
579func thoudostbitethythumb(userid int64, who []string, objid string) bool {
580	thumblock.Lock()
581	biters := thumbbiters[userid]
582	thumblock.Unlock()
583	for _, w := range who {
584		if biters[w] {
585			return true
586		}
587		where := originate(w)
588		if where != "" {
589			if biters[where] {
590				return true
591			}
592		}
593	}
594	return false
595}
596
597func keymatch(keyname string, actor string) string {
598	hash := strings.IndexByte(keyname, '#')
599	if hash == -1 {
600		hash = len(keyname)
601	}
602	owner := keyname[0:hash]
603	if owner == actor {
604		return originate(actor)
605	}
606	return ""
607}