7de2e36da30e draft — Peter Sanchez tip 25 days ago
merged tedu upstream
9 files changed, 229 insertions(+), 15 deletions(-)

M .hgignore
M activity.go
M database.go
M fun.go
M honk.go
M views/honk.html
M views/honkers.html
M views/honkform.html
M web.go
M .hgignore +1 -0
@@ 7,6 7,7 @@ attachments
 memes
 emus
 honk
+avatarcache
 violations.json
 docs/activitypub.7.html
 docs/hfcs.1.html

          
M activity.go +15 -0
@@ 186,6 186,9 @@ func GetJunkTimeout(userid UserID, url s
 			Fixup:   sign,
 			Limit:   1 * 1024 * 1024,
 		})
+		if err != nil {
+			dlog.Printf("Outbound (GetJunkTimeout) Request: %v Failed! %v", url, err)
+		}
 		return j, err
 	}
 

          
@@ 1983,6 1986,18 @@ func somethingabout(obj junk.Junk) (*Som
 	if info.Owner == "" {
 		info.Owner = info.XID
 	}
+
+	iconInfo, ok := obj.GetMap("icon")
+	if ok {
+		mType, _ := iconInfo.GetString("mediaType")
+		if strings.HasPrefix(mType, "image/") {
+			AvatarUrl, ok := iconInfo.GetString("url")
+			if ok {
+				info.AvatarURL = AvatarUrl
+			}
+		}
+	}
+
 	return info, nil
 }
 

          
M database.go +31 -1
@@ 1077,6 1077,30 @@ func cleanupdb(arg string) {
 	cleanupfiles()
 }
 
+func getusercount() int {
+	row := stmtGetUserCount.QueryRow()
+	var count int
+	row.Scan(&count)
+	return count
+}
+
+func getactiveusercount(monthsago int) int {
+	origin := time.Now().AddDate(0, -monthsago, 0).UTC().Format(dbtimeformat)
+	row := stmtGetActiveUserCount.QueryRow(origin)
+
+	var count int
+	row.Scan(&count)
+	return count
+}
+
+func getlocalhonkcount() int {
+	row := stmtGetLocalHonkCount.QueryRow()
+
+	var count int
+	row.Scan(&count)
+	return count
+}
+
 var stmtHonkers, stmtDubbers, stmtNamedDubbers, stmtSaveHonker, stmtUpdateFlavor, stmtUpdateHonker *sql.Stmt
 var stmtDeleteHonker *sql.Stmt
 var stmtAnyXonk, stmtOneXonk, stmtPublicHonks, stmtUserHonks, stmtHonksByCombo, stmtHonksByConvoy *sql.Stmt

          
@@ 1095,6 1119,9 @@ var stmtSaveMeta, stmtDeleteAllMeta, stm
 var stmtHonksISaved, stmtGetFilters, stmtSaveFilter, stmtDeleteFilter *sql.Stmt
 var stmtGetTracks *sql.Stmt
 var stmtSaveChonk, stmtLoadChonks, stmtGetChatters *sql.Stmt
+var stmtGetUserCount *sql.Stmt
+var stmtGetActiveUserCount *sql.Stmt
+var stmtGetLocalHonkCount *sql.Stmt
 var stmtDeliquentCheck, stmtDeliquentUpdate *sql.Stmt
 var stmtGetBlobData, stmtSaveBlobData *sql.Stmt
 

          
@@ 1122,7 1149,7 @@ func closedatabases() {
 }
 
 func prepareStatements(db *sql.DB) {
-	stmtHonkers = preparetodie(db, "select honkerid, userid, name, xid, flavor, combos, meta from honkers where userid = ? and (flavor = 'presub' or flavor = 'sub' or flavor = 'peep' or flavor = 'unsub') order by name")
+	stmtHonkers = preparetodie(db, "select honkerid, userid, name, xid, flavor, combos, meta from honkers where userid = ? and (flavor = 'presub' or flavor = 'sub' or flavor = 'peep' or flavor = 'unsub') order by name collate nocase")
 	stmtSaveHonker = preparetodie(db, "insert into honkers (userid, name, xid, flavor, combos, owner, meta, folxid) values (?, ?, ?, ?, ?, ?, ?, '')")
 	stmtUpdateFlavor = preparetodie(db, "update honkers set flavor = ?, folxid = ? where userid = ? and name = ? and xid = ? and flavor = ?")
 	stmtUpdateHonker = preparetodie(db, "update honkers set name = ?, combos = ?, meta = ? where honkerid = ? and userid = ?")

          
@@ 1206,6 1233,9 @@ func prepareStatements(db *sql.DB) {
 	stmtSaveChonk = preparetodie(db, "insert into chonks (userid, xid, who, target, dt, noise, format) values (?, ?, ?, ?, ?, ?, ?)")
 	stmtLoadChonks = preparetodie(db, "select chonkid, userid, xid, who, target, dt, noise, format from chonks where userid = ? and dt > ? and chonkid > ? order by chonkid asc")
 	stmtGetChatters = preparetodie(db, "select distinct(target) from chonks where userid = ?")
+	stmtGetUserCount = preparetodie(db, "select count(*) from users where userid > 0")
+	stmtGetActiveUserCount = preparetodie(db, "select count(distinct honker) from honks where whofore = 2 and dt > ?")
+	stmtGetLocalHonkCount = preparetodie(db, "select count(*) from honks where whofore = 2")
 	stmtDeliquentCheck = preparetodie(db, "select dooverid, msg from doovers where userid = ? and rcpt = ?")
 	stmtDeliquentUpdate = preparetodie(db, "update doovers set msg = ? where dooverid = ?")
 	g_blobdb = openblobdb()

          
M fun.go +6 -2
@@ 471,7 471,7 @@ var re_emus = regexp.MustCompile(`:[[:al
 
 var emucache = gencache.New(gencache.Options[string, *Emu]{Fill: func(ename string) (*Emu, bool) {
 	fname := ename[1 : len(ename)-1]
-	exts := []string{".png", ".gif"}
+	exts := []string{".png", ".gif", ".svg"}
 	for _, ext := range exts {
 		_, err := os.Stat(dataDir + "/emus/" + fname + ext)
 		if err != nil {

          
@@ 481,7 481,11 @@ var emucache = gencache.New(gencache.Opt
 		if develMode {
 			url = fmt.Sprintf("/emu/%s%s", fname, ext)
 		}
-		return &Emu{ID: url, Name: ename, Type: "image/" + ext[1:]}, true
+		e := &Emu{ID: url, Name: ename, Type: "image/" + ext[1:]}
+		if ext == ".svg" {
+			e.Type = e.Type + "+xml" // image/svg+xml
+		}
+		return e, true
 	}
 	return nil, true
 }, Duration: 10 * time.Second})

          
M honk.go +5 -4
@@ 280,10 280,11 @@ type HonkerMeta struct {
 }
 
 type SomeThing struct {
-	What  int
-	XID   string
-	Owner string
-	Name  string
+	What      int
+	XID       string
+	Owner     string
+	Name      string
+	AvatarURL string
 }
 
 const (

          
M views/honk.html +3 -3
@@ 10,7 10,7 @@ 
 {{ else }}
 <a href="{{ .Honker }}" rel=noreferrer>
 {{ end }}
-<img alt="" src="/a?a={{ .Honker}}">
+<img alt="" src="/a?a={{ .Honker}}" loading="lazy">
 </a>
 {{ if .Oonker }}
 {{ if $bonkcsrf }}

          
@@ 18,7 18,7 @@ 
 {{ else }}
 <a href="{{ .Oonker }}" rel=noreferrer>
 {{ end }}
-<img alt="" src="/a?a={{ .Oonker}}">
+<img alt="" src="/a?a={{ .Oonker}}" loading="lazy">
 </a>
 {{ end }}
 <p>

          
@@ 85,7 85,7 @@ in reply to: <a href="{{ .RID }}" rel=no
 {{ if eq .Media "video/mp4" }}
 <p><video controls src="{{ .URL }}">{{ .Name }}</video></p>
 {{ else }}
-<p><img src="{{ .URL }}" title="{{ .Desc }}" alt="{{ .Desc }}"></p>
+<p><img src="{{ .URL }}" title="{{ .Desc }}" alt="{{ .Desc }}" loading="lazy">
 {{ end }}
 {{ end }}
 {{ end }}

          
M views/honkers.html +1 -1
@@ 34,7 34,7 @@ 
 {{ end }}
 <section class="honk">
 <header>
-<img alt="avatar" src="/a?a={{ .XID }}">
+<img alt="avatar" src="/a?a={{ .XID }}" loading="lazy">
 <p class="font18em"><a href="/h/{{ .Name }}">{{ .Name }}</a>
 </header>
 <p>

          
M views/honkform.html +20 -0
@@ 63,3 63,23 @@ 
 <button name="preview" value="preview">preview</button>
 <button type=button name="cancel" value="cancel">cancel</button>
 </form>
+<script>
+function addemu(data){
+	const box = document.getElementById("honknoise");
+	box.value += data;
+}
+function loademus() {
+	div = document.getElementById("emupicker")
+	request = new XMLHttpRequest();
+	request.open('GET', '/emus')
+	request.onload = function(){
+		div.innerHTML = request.responseText
+	}
+	if (div.style.display === "none") {
+		div.style.display = "block";
+	} else {
+		div.style.display = "none";
+	}
+	request.send()
+}
+</script>

          
M web.go +147 -4
@@ 17,8 17,10 @@ package main
 
 import (
 	"bytes"
+	"crypto/sha256"
 	"crypto/sha512"
 	"database/sql"
+	"encoding/hex"
 	"errors"
 	"fmt"
 	"html/template"

          
@@ 31,6 33,7 @@ import (
 	"net/url"
 	"os"
 	"os/signal"
+	gofilepath "path/filepath"
 	"regexp"
 	"runtime/pprof"
 	"sort"

          
@@ 2497,12 2500,19 @@ func dochpass(w http.ResponseWriter, r *
 var oldfingers = gencache.New(gencache.Options[string, []byte]{Fill: func(orig string) ([]byte, bool) {
 	if strings.HasPrefix(orig, "acct:") {
 		orig = orig[5:]
+	} else {
+		orig, _ = url.QueryUnescape(orig)
 	}
 	name := orig
 	idx := strings.LastIndexByte(name, '/')
 	if idx != -1 {
 		name = name[idx+1:]
-		if serverURL("/%s/%s", userSep, name) != orig {
+		url := serverURL("/%s/%s", userSep, name)
+		if strings.HasPrefix(name, "@") {
+			url = fmt.Sprintf("https://%s/%s", serverName, name)
+			name = name[1:]
+		}
+		if url != orig {
 			ilog.Printf("foreign request rejected")
 			name = ""
 		}

          
@@ 2528,7 2538,17 @@ var oldfingers = gencache.New(gencache.O
 	l["rel"] = "self"
 	l["type"] = `application/activity+json`
 	l["href"] = user.URL
-	j["links"] = []junk.Junk{l}
+
+	l3 := junk.New()
+	l3["rel"] = "http://webfinger.net/rel/avatar"
+	ext := gofilepath.Ext(user.Options.Avatar)
+	if ext[1:] == "jpg" {
+		l3["type"] = "image/jpeg"
+	} else if ext[1:] == "png" {
+		l3["type"] = "image/png"
+	}
+	l3["href"] = user.Options.Avatar
+	j["links"] = []junk.Junk{l, l3}
 	return j.ToBytes(), true
 }})
 

          
@@ 2546,11 2566,73 @@ func fingerlicker(w http.ResponseWriter,
 	}
 }
 
+func knowninformation(w http.ResponseWriter, r *http.Request) {
+	j := junk.New()
+	l := junk.New()
+
+	l["rel"] = `http://nodeinfo.diaspora.software/ns/schema/2.0`
+	l["href"] = fmt.Sprintf("https://%s/nodeinfo/2.0", serverName)
+	j["links"] = []junk.Junk{l}
+
+	w.Header().Set("Content-Type", "application/json")
+	j.Write(w)
+}
+
+func actualinformation(w http.ResponseWriter, r *http.Request) {
+	j := junk.New()
+
+	soft := junk.New()
+	soft["name"] = "honk"
+	soft["version"] = softwareVersion
+
+	services := junk.New()
+	services["inbound"] = []string{}
+	services["outbound"] = []string{"rss2.0"}
+
+	users := junk.New()
+	users["total"] = getusercount()
+	users["activeHalfyear"] = getactiveusercount(6)
+	users["activeMonth"] = getactiveusercount(1)
+
+	usage := junk.New()
+	usage["users"] = users
+	usage["localPosts"] = getlocalhonkcount()
+
+	j["version"] = "2.0"
+	j["protocols"] = []string{"activitypub"}
+	j["software"] = soft
+	j["services"] = services
+	j["openRegistrations"] = false
+	j["usage"] = usage
+
+	w.Header().Set("Content-Type", "application/json")
+	j.Write(w)
+}
+
 func somedays() string {
 	secs := 432000 + notrand.Int63n(432000)
 	return fmt.Sprintf("%d", secs)
 }
 
+type avaKey struct {
+	uid int64
+	url string
+}
+
+func isURL(s string) bool {
+	u, err := url.Parse(s)
+	return err == nil && u.Scheme != "" && u.Host != ""
+}
+
+func avatateautogen(w http.ResponseWriter, r *http.Request) {
+	n := r.FormValue("a")
+	a := genAvatar(n)
+	if !develMode {
+		w.Header().Set("Cache-Control", "max-age="+somedays())
+	}
+	w.Write(a)
+}
+
 func lookatme(ava string) string {
 	if strings.Contains(ava, serverName+"/"+userSep) {
 		idx := strings.LastIndexByte(ava, '/')

          
@@ 2569,16 2651,75 @@ func avatate(w http.ResponseWriter, r *h
 	if develMode {
 		loadAvatarColors()
 	}
+	var uid int64
 	n := r.FormValue("a")
+
+	if !isURL(n) {
+		avatateautogen(w, r)
+		return
+	}
+
+	hasher := sha256.New()
+	hasher.Write([]byte(n))
+	hashString := hex.EncodeToString(hasher.Sum(nil))
+
+	fileKey := fmt.Sprintf("%s/avatarcache/%s", dataDir, hashString)
+	s, err := os.Stat(fileKey)
+	if err == nil {
+		if time.Since(s.ModTime()) < (time.Hour * 24 * 7) {
+			b, _ := os.ReadFile(fileKey)
+			w.Header().Set("Content-Type", http.DetectContentType(b))
+			if !develMode {
+				w.Header().Set("X-Content-Type-Options", "nosniff")
+				w.Header().Set("Cache-Control", "max-age="+somedays())
+			}
+			w.Write(b)
+			return
+		}
+	}
+
 	if redir := lookatme(n); redir != "" {
 		http.Redirect(w, r, redir, http.StatusSeeOther)
 		return
 	}
-	a := genAvatar(n)
+
 	if !develMode {
 		w.Header().Set("Cache-Control", "max-age="+somedays())
 	}
-	w.Write(a)
+
+	uinfo := login.GetUserInfo(r)
+	if uinfo != nil {
+		uid = uinfo.UserID
+	} else {
+		uid = 0
+	}
+
+	// Else, we fetch it now
+	xid := n
+	// j, err := GetJunk(u.UserID, xid)
+	j, err := GetJunk(UserID(uid), xid)
+	if err != nil {
+		avatateautogen(w, r)
+		return
+	}
+
+	info, _ := somethingabout(j)
+	if info.AvatarURL == "" {
+		avatateautogen(w, r)
+		return
+	}
+	imageBytes, err := fetchsome(info.AvatarURL)
+	if err != nil {
+		avatateautogen(w, r)
+		return
+	}
+	w.Header().Set("Content-Type", http.DetectContentType(imageBytes))
+	w.Write(imageBytes)
+
+	go func() {
+		os.MkdirAll(fmt.Sprintf("%s/avatarcache/", dataDir), 0755)
+		os.WriteFile(fileKey, imageBytes, 0644)
+	}()
 }
 
 func serveviewasset(w http.ResponseWriter, r *http.Request) {

          
@@ 3155,6 3296,8 @@ func serve() {
 	getters.HandleFunc("/emu/{emu:[^.]*[^/]+}", serveemu)
 	getters.HandleFunc("/meme/{meme:[^.]*[^/]+}", servememe)
 	getters.HandleFunc("/.well-known/webfinger", fingerlicker)
+	getters.HandleFunc("/.well-known/nodeinfo", knowninformation)
+	getters.HandleFunc("/nodeinfo/2.0", actualinformation)
 
 	getters.HandleFunc("/flag/{code:.+}", showflag)