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)