4b126230542e draft — Peter Sanchez tip 3 days ago
Merge tedu upstream
M activity.go +52 -12
@@ 591,12 591,12 @@ func grabhonk(user *WhatAbout, xid strin
 	xonksaver(user, j, originate(final))
 }
 
-var re_mast0link = regexp.MustCompile(`https://[[:alnum:].]+/users/[[:alnum:]]+/statuses/[[:digit:]]+`)
-var re_masto1ink = regexp.MustCompile(`https://([[:alnum:].]+)/@([[:alnum:]]+)(@[[:alnum:].]+)?/([[:digit:]]+)`)
-var re_misslink = regexp.MustCompile(`https://[[:alnum:].]+/notes/[[:alnum:]]+`)
-var re_honklink = regexp.MustCompile(`https://[[:alnum:].]+/u/[[:alnum:]]+/h/[[:alnum:]]+`)
-var re_r0malink = regexp.MustCompile(`https://[[:alnum:].]+/objects/[[:alnum:]-]+`)
-var re_roma1ink = regexp.MustCompile(`https://[[:alnum:].]+/notice/[[:alnum:]]+`)
+var re_mast0link = regexp.MustCompile(`https://[[:alnum:].-]+/users/[[:alnum:]_]+/statuses/[[:digit:]]+`)
+var re_masto1ink = regexp.MustCompile(`https://([[:alnum:].-]+)/@([[:alnum:]_]+)(@[[:alnum:].]+)?/([[:digit:]]+)`)
+var re_misslink = regexp.MustCompile(`https://[[:alnum:].-]+/notes/[[:alnum:]]+`)
+var re_honklink = regexp.MustCompile(`https://[[:alnum:].-]+/u/[[:alnum:]_]+/h/[[:alnum:]]+`)
+var re_r0malink = regexp.MustCompile(`https://[[:alnum:].-]+/objects/[[:alnum:]-]+`)
+var re_roma1ink = regexp.MustCompile(`https://[[:alnum:].-]+/notice/[[:alnum:]]+`)
 var re_qtlinks = regexp.MustCompile(`>https://[^\s<]+<`)
 
 func xonksaver(user *WhatAbout, item junk.Junk, origin string) *Honk {

          
@@ 1045,10 1045,24 @@ func xonksaver2(user *WhatAbout, item ju
 					if ua, ok := att.GetArray("url"); ok && len(ua) > 0 {
 						u, ok = ua[0].(string)
 						if !ok {
-							if uu, ok := ua[0].(junk.Junk); ok {
-								u, _ = uu.GetString("href")
-								if mt == "" {
-									mt, _ = uu.GetString("mediaType")
+							mtprio := -1
+							for _, item := range ua {
+								if uu, ok := item.(junk.Junk); ok {
+									p := 0
+									m, _ := uu.GetString("mediaType")
+									switch m {
+									case "image/jpeg":
+										p = 1
+									case "image/avif":
+										if acceptAVIF {
+											p = 2
+										}
+									}
+									if p > mtprio {
+										mtprio = p
+										u, _ = uu.GetString("href")
+										mt = m
+									}
 								}
 							}
 						}

          
@@ 1411,11 1425,22 @@ func activatedonks(donks []*Donk) []junk
 			continue
 		}
 		jd := junk.New()
-		jd["mediaType"] = d.Media
 		jd["name"] = d.Name
 		jd["summary"] = html.EscapeString(d.Desc)
 		jd["type"] = "Document"
-		jd["url"] = d.URL
+		if convertAVIF && d.Media == "image/jpeg" {
+			var u [2]junk.Junk
+			u[0] = junk.New()
+			u[0]["mediaType"] = "image/jpeg"
+			u[0]["href"] = d.URL
+			u[1] = junk.New()
+			u[1]["mediaType"] = "image/avif"
+			u[1]["href"] = d.URL + ".avif"
+			jd["url"] = u
+		} else {
+			jd["mediaType"] = d.Media
+			jd["url"] = d.URL
+		}
 		atts = append(atts, jd)
 	}
 	return atts

          
@@ 1758,7 1783,22 @@ func sendchonk(user *WhatAbout, ch *Chon
 	}
 }
 
+func trigger(user *WhatAbout, honk *Honk) {
+	j := junk.New()
+	j["honk"] = honk
+	ki := ziggy(user.ID)
+	if ki == nil {
+		return
+	}
+	err := PostJunk(ki.keyname, ki.seckey, user.Options.Trigger, j)
+	if err != nil {
+		ilog.Printf("error triggering: %s", err)
+	}
+}
 func honkworldwide(user *WhatAbout, honk *Honk) {
+	if user.Options.Trigger != "" {
+		trigger(user, honk)
+	}
 	jonk, _ := jonkjonk(user, honk)
 	jonk["@context"] = itiswhatitis
 	msg := jonk.ToBytes()

          
M bloat.go +0 -35
@@ 14,38 14,3 @@ 
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
 package main
-
-import (
-	"io"
-	"net"
-	"time"
-)
-
-func qotd() {
-	var qotdaddr string
-	getconfig("qotdaddr", &qotdaddr)
-	if qotdaddr == "" {
-		return
-	}
-	s, err := net.Listen("tcp", ":8017")
-	if err != nil {
-		return
-	}
-	for {
-		c, err := s.Accept()
-		if err != nil {
-			time.Sleep(time.Second)
-			continue
-		}
-		honks := getpublichonks()
-		for _, honk := range honks {
-			if !firstclass(honk) {
-				continue
-			}
-			io.WriteString(c, honk.Noise)
-			io.WriteString(c, "\n")
-			break
-		}
-		c.Close()
-	}
-}

          
M database.go +8 -3
@@ 269,8 269,13 @@ func gethonksfromlongago(userid UserID, 
 	params = append(params, wanted)
 	params = append(params, userid)
 	now := time.Now()
-	for i := 1; i <= 6; i++ {
-		dt := time.Date(now.Year()-i, now.Month(), now.Day(), now.Hour(), now.Minute(),
+
+	for i := 1; i <= 100; i++ {
+		yr := now.Year() - i
+		if yr < 2019 {
+			break
+		}
+		dt := time.Date(yr, now.Month(), now.Day(), now.Hour(), now.Minute(),
 			now.Second(), 0, now.Location())
 		dt1 := dt.Add(-36 * time.Hour).UTC().Format(dbtimeformat)
 		dt2 := dt.Add(12 * time.Hour).UTC().Format(dbtimeformat)

          
@@ 1204,7 1209,7 @@ func prepareStatements(db *sql.DB) {
 	stmtCheckFileHash = preparetodie(db, "select xid from filehashes where hash = ?")
 	stmtGetFileMedia = preparetodie(db, "select media from filehashes where xid = ?")
 	stmtFindXonk = preparetodie(db, "select honkid from honks where userid = ? and xid = ?")
-	stmtGetFileInfo = preparetodie(db, "select url from filemeta where xid = ?")
+	stmtGetFileInfo = preparetodie(db, "select url, media, meta from filemeta where xid = ?")
 	stmtFindFile = preparetodie(db, "select fileid, xid from filemeta where url = ? and local = 1")
 	stmtFindFileId = preparetodie(db, "select xid, local, description from filemeta where fileid = ? and url = ? and local = 1")
 	stmtUserByName = preparetodie(db, "select userid, username, displayname, about, pubkey, seckey, options from users where username = ? and userid > 0")

          
M docs/changelog.txt +8 -0
@@ 1,5 1,13 @@ 
 changelog
 
+### next
+
++ Triggers.
+
++ AVIF image support. Optional with config option convertavif.
+
+- Remove the qotd server.
+
 ### 1.4.2 Kindred Key
 
 + Fix shortcuts for alternate keymaps.

          
M docs/honk.1 +4 -3
@@ 214,12 214,13 @@ See
 .Xr honk 8
 for more about the funzone.
 .Pp
+A web trigger may be set which will receive POSTs when new honks are posted.
+.Dq http://spybridge.honk.example/trigger
+.Pp
 Some options to customize the site appearance:
-.Bl -tag -width skinny
+.Bl -tag -width reaction
 .It skinny
 Use a narrower column for the main display.
-.It omit images
-Omit img tags, to lighten page loads on slow connections.
 .It apple
 Prefer Apple links for maps.
 The default is OpenStreetMap.

          
M docs/honk.8 +2 -0
@@ 260,6 260,8 @@ Custom URL seperators (not "u" and "h") 
 e.g. example.com/users/username/honk/somehonk instead of
 example.com/u/username/h/somehonk.
 .Bl -tag -width collectforwards
+.It convertavif
+Support AVIF images if libavif can be found.
 .It fasttimeout
 Short timeout for fetching activities.
 (Default: 5)

          
M filestoragemanagerfactory.go +28 -10
@@ 35,6 35,19 @@ func hashfiledata(data []byte) string {
 	return fmt.Sprintf("%x", h.Sum(nil))
 }
 
+func needfilehash(data []byte, xid *string) string {
+	hash := hashfiledata(data)
+	row := stmtCheckFileHash.QueryRow(hash)
+	err := row.Scan(xid)
+	if err == nil {
+		return ""
+	}
+	if err != sql.ErrNoRows {
+		elog.Printf("error checking file hash: %s", err)
+	}
+	return hash
+}
+
 func filepath(xid string) string {
 	parts := strings.SplitN(xid, ".", 2)
 	subdir := "xx"

          
@@ 65,10 78,7 @@ func savefiledata(xid string, data []byt
 func savefileandxid(name string, desc string, url string, media string, local bool, data []byte, meta *DonkMeta) (int64, string, error) {
 	var xid string
 	if local {
-		hash := hashfiledata(data)
-		row := stmtCheckFileHash.QueryRow(hash)
-		err := row.Scan(&xid)
-		if err == sql.ErrNoRows {
+		if hash := needfilehash(data, &xid); hash != "" {
 			xid = xfildate()
 			switch media {
 			case "image/png":

          
@@ 82,16 92,13 @@ func savefileandxid(name string, desc st
 			case "text/plain":
 				xid += ".txt"
 			}
-			err = savefiledata(xid, data)
+			err := savefiledata(xid, data)
 			if err == nil {
 				_, err = stmtSaveFileHash.Exec(xid, hash, media)
 			}
 			if err != nil {
 				return 0, "", err
 			}
-		} else if err != nil {
-			elog.Printf("error checking file hash: %s", err)
-			return 0, "", err
 		}
 		if url == "" {
 			url = serverURL("/d/%s", xid)

          
@@ 113,9 120,11 @@ func savefileandxid(name string, desc st
 func getfileinfo(xid string) *Donk {
 	donk := new(Donk)
 	row := stmtGetFileInfo.QueryRow(xid)
-	err := row.Scan(&donk.URL)
+	var j string
+	err := row.Scan(&donk.URL, &donk.Media, &j)
 	if err == nil {
 		donk.XID = xid
+		unjsonify(j, &donk.Meta)
 		return donk
 	}
 	if err != sql.ErrNoRows {

          
@@ 194,7 203,9 @@ func loaddata(xid string) ([]byte, func(
 }
 
 func servefiledata(w http.ResponseWriter, r *http.Request, xid string) {
-	if strings.HasSuffix(xid, ".avif") {
+	wantAVIF := false
+	if convertAVIF && strings.HasSuffix(xid, ".avif") {
+		wantAVIF = true
 		xid = xid[:len(xid)-5]
 	}
 	var media string

          
@@ 219,6 230,13 @@ func servefiledata(w http.ResponseWriter
 			data = img.Data
 		}
 	}
+	if wantAVIF {
+		d2 := avifEncode(data)
+		if d2 != nil {
+			data = d2
+			media = "image/avif"
+		}
+	}
 	w.Header().Set("Content-Type", media)
 	w.Header().Set("X-Content-Type-Options", "nosniff")
 	w.Header().Set("Cache-Control", "max-age="+somedays())

          
M go.mod +1 -1
@@ 11,7 11,7 @@ require (
 	humungus.tedunangst.com/r/go-sqlite3 v1.2.1
 	humungus.tedunangst.com/r/gonix v0.1.4
 	humungus.tedunangst.com/r/termvc v0.1.3
-	humungus.tedunangst.com/r/webs v0.7.23
+	humungus.tedunangst.com/r/webs v0.7.26
 )
 
 require (

          
M go.sum +2 -2
@@ 80,5 80,5 @@ humungus.tedunangst.com/r/gonix v0.1.4 h
 humungus.tedunangst.com/r/gonix v0.1.4/go.mod h1:VFBc2bPDXr1ayHOmHUutxYu8fSM+pkwK8o36h4rkORg=
 humungus.tedunangst.com/r/termvc v0.1.3 h1:BYxcqdA2Ijhqolf2BdNlGw5355qE80EzAqiNgi7d5tk=
 humungus.tedunangst.com/r/termvc v0.1.3/go.mod h1:TnlG9PbH77OpEf46iDyb/H9drjegQNwhpXalmGGrbhU=
-humungus.tedunangst.com/r/webs v0.7.23 h1:LEamoWvtgBOukGuzHj/T1qhRwNQkoz2RMCiwvvxwIug=
-humungus.tedunangst.com/r/webs v0.7.23/go.mod h1:ylhqHSPI0Oi7b4nsnx5mSO7AjLXN7wFpEHayLfN/ugk=
+humungus.tedunangst.com/r/webs v0.7.26 h1:Sfak8XRalfEYHiE1gh2ZW6wSxCq8tBxp8FxD5iD9c5Y=
+humungus.tedunangst.com/r/webs v0.7.26/go.mod h1:ylhqHSPI0Oi7b4nsnx5mSO7AjLXN7wFpEHayLfN/ugk=

          
M honk.go +1 -0
@@ 53,6 53,7 @@ type UserOptions struct {
 	ChatPubKey   string
 	ChatSecKey   string
 	TOTP         string `json:",omitempty"`
+	Trigger      string `json:",omitempty"`
 }
 
 type KeyInfo struct {

          
M main.go +12 -4
@@ 50,19 50,18 @@ var serverMsg template.HTML
 var aboutMsg template.HTML
 var loginMsg template.HTML
 var collectForwards = true
+var convertAVIF = false
+var acceptAVIF = false
 
 func serverURL(u string, args ...interface{}) string {
 	return fmt.Sprintf("https://"+serverName+u, args...)
 }
 
 func ElaborateUnitTests() {
-	data, _ := os.ReadFile("input.jpg")
-	d2 := avifEncode(data)
-	os.WriteFile("output.avif", d2, 0600)
 }
 
 func avifEncode(data []byte) []byte {
-	return lazif.Encode(data)
+	return lazif.EncodeJPEG(data)
 }
 
 func unplugserver(hostname string) {

          
@@ 185,6 184,15 @@ func main() {
 	getconfig("honkwindow", &honkwindow)
 	honkwindow *= 24 * time.Hour
 	getconfig("collectforwards", &collectForwards)
+	getconfig("convertavif", &convertAVIF)
+	if convertAVIF {
+		if !lazif.Load().HasAVIF() {
+			elog.Printf("libavif could not be loaded")
+			convertAVIF = false
+		} else {
+			getconfig("acceptavif", &acceptAVIF)
+		}
+	}
 
 	prepareStatements(db)
 

          
M views/account.html +12 -5
@@ 7,19 7,26 @@ 
 <form id="aboutform" action="/saveuser" method="POST">
 <input type="hidden" name="CSRF" value="{{ .UserCSRF }}">
 <p>about me:
-<p><textarea name="whatabout">{{ .WhatAbout }}</textarea>
+<br><textarea tabindex=1 name="whatabout">{{ .WhatAbout }}</textarea>
+
+<p>trigger:
+<br><input tabindex=1 name="trigger" value="{{ .User.Options.Trigger }}">
+
 <p><label class="button" for="skinny">skinny layout:</label>
 <input tabindex=1 type="checkbox" id="skinny" name="skinny" value="skinny" {{ if .User.Options.SkinnyCSS }}checked{{ end }}><span></span>
-<p><label class="button" for="omitimages">omit images:</label>
-<input tabindex=1 type="checkbox" id="omitimages" name="omitimages" value="omitimages" {{ if .User.Options.OmitImages }}checked{{ end }}><span></span>
+
 <p><label class="button" for="mentionall">mention all:</label>
 <input tabindex=1 type="checkbox" id="mentionall" name="mentionall" value="mentionall" {{ if .User.Options.MentionAll }}checked{{ end }}><span></span>
+
 <p><label class="button" for="inlineqts">inline quotes:</label>
 <input tabindex=1 type="checkbox" id="inlineqts" name="inlineqts" value="inlineqts" {{ if .User.Options.InlineQuotes }}checked{{ end }}><span></span>
+
 <p><label class="button" for="maps">apple map links:</label>
 <input tabindex=1 type="checkbox" id="maps" name="maps" value="apple" {{ if eq "apple" .User.Options.MapLink }}checked{{ end }}><span></span>
+
 <p><label class="button" for="enabletotp">make logins hard:</label>
 <input tabindex=1 type="checkbox" id="enabletotp" name="enabletotp" value="enabletotp" {{ if .User.Options.TOTP }}checked{{ end }}><span></span>
+
 <p><label class="button" for="reaction">reaction:</label>
 <select tabindex=1 name="reaction">
 <option {{ and (eq .User.Options.Reaction "none") "selected" }}>none</option>

          
@@ 32,7 39,7 @@ 
 <option {{ and (eq .User.Options.Reaction "\U0001FA93") "selected" }}>{{ "\U0001FA93" }}</option>
 <option {{ and (eq .User.Options.Reaction "\U0001F9EF") "selected" }}>{{ "\U0001F9EF" }}</option>
 </select>
-<p><button>update settings</button>
+<p><button tabindex=1>update settings</button>
 </form>
 </div>
 <hr>

          
@@ 42,7 49,7 @@ 
 <p>change password
 <p><input tabindex=1 type="password" name="oldpass"> - oldpass
 <p><input tabindex=1 type="password" name="newpass"> - newpass
-<p><button>change</button>
+<p><button tabindex=1>change</button>
 </form>
 </div>
 {{ if .User.Options.TOTP }}

          
M views/common.js +1 -1
@@ 1,7 1,7 @@ 
 
 let re_link = /https?:[^ ]*/
 function hotkey(e) {
-	if (e.ctrlKey || e.altKey)
+	if (e.ctrlKey || e.altKey || e.metaKey)
 		return
 	if (e.code == "Escape") {
 		var menu = document.getElementById("topmenu")

          
M views/honk.html +0 -1
@@ 2,7 2,6 @@ 
 {{ $bonkcsrf := .BonkCSRF }}
 {{ $IsPreview := .IsPreview }}
 {{ $maplink := .MapLink }}
-{{ $omitimages := .OmitImages }}
 {{ with .Honk }}
 <header>
 {{ if $bonkcsrf }}

          
M web.go +18 -6
@@ 1563,7 1563,11 @@ func (d *Donk) HTML() template.HTML {
 		if d.Media == "text/plain" || d.Media == "application/pdf" {
 			return templates.Sprintf(`<p><a href="/d/%s">Attachment: %s</a>%s (%d)</p>`, d.XID, d.Name, desc, d.Meta.Length)
 		} else {
-			return templates.Sprintf(`<picture><source type="image/avif" srcset="/d/%s.avif"><img class="donk donklink" src="/d/%s" loading=lazy title="%s" alt="%s" width="%d" height="%d"></picture>`, d.XID, d.XID, d.Desc, d.Desc, d.Meta.Width, d.Meta.Height)
+			var sources template.HTML
+			if convertAVIF && d.Media == "image/jpeg" {
+				sources += templates.Sprintf(`<source type="image/avif" srcset="/d/%s.avif">`, d.XID)
+			}
+			return templates.Sprintf(`<picture>%s<img class="donk donklink" src="/d/%s" loading=lazy title="%s" alt="%s" width="%d" height="%d"></picture>`, sources, d.XID, d.Desc, d.Desc, d.Meta.Width, d.Meta.Height)
 		}
 	} else {
 		if d.External {

          
@@ 1584,8 1588,8 @@ func saveuser(w http.ResponseWriter, r *
 	db := opendatabase()
 
 	options := user.Options
+	options.Trigger = r.FormValue("trigger")
 	options.SkinnyCSS = r.FormValue("skinny") == "skinny"
-	options.OmitImages = r.FormValue("omitimages") == "omitimages"
 	options.MentionAll = r.FormValue("mentionall") == "mentionall"
 	options.InlineQuotes = r.FormValue("inlineqts") == "inlineqts"
 	options.MapLink = r.FormValue("maps")

          
@@ 1976,10 1980,19 @@ func formtodonk(w http.ResponseWriter, r
 	io.Copy(&buf, file)
 	file.Close()
 	data := buf.Bytes()
-	var media, name string
+	var xid, media, name string
 	var donkmeta DonkMeta
-	img, err := bigshrink(data)
-	if err == nil {
+	if needfilehash(data, &xid) == "" {
+		d := getfileinfo(xid)
+		if d == nil {
+			elog.Printf("lost a file xid somehow: %s", xid)
+		} else {
+			name = d.Name
+			donkmeta.Width = d.Meta.Width
+			donkmeta.Height = d.Meta.Height
+			media = d.Media
+		}
+	} else if img, err := bigshrink(data); err == nil {
 		data = img.Data
 		donkmeta.Width = img.Width
 		donkmeta.Height = img.Height

          
@@ 3280,7 3293,6 @@ func serve() {
 	go tracker()
 	go syndicator()
 	go bgmonitor()
-	go qotd()
 	loadLingo()
 	emuinit()