M .hgignore +1 -0
@@ 6,6 6,7 @@ syntax: glob
memes
emus
honk
+avatarcache
violations.json
docs/activitypub.7.html
docs/hfcs.1.html
M activity.go +15 -0
@@ 165,6 165,9 @@ func GetJunkTimeout(userid int64, url st
Client: &client,
Fixup: sign,
})
+ if err != nil {
+ dlog.Printf("Outbound (GetJunkTimeout) Request: %v Failed! %v", url, err)
+ }
return j, err
}
@@ 1745,6 1748,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
@@ 1145,6 1145,30 @@ func cleanupdb(arg string) {
}
}
+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
@@ 1163,6 1187,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
func preparetodie(db *sql.DB, s string) *sql.Stmt {
@@ 1174,7 1201,7 @@ func preparetodie(db *sql.DB, s string)
}
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 = ?")
@@ 1250,6 1277,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 > ? 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 = ?")
}
M honk.go +5 -4
@@ 253,10 253,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 +4 -4
@@ 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>
@@ 72,7 72,7 @@ in reply to: <a href="{{ .RID }}" rel=no
{{ if $omitimages }}
<p><a href="/d/{{ .XID }}">Image: {{ .Name }}</a>{{ if not (eq .Desc .Name) }} {{ .Desc }}{{ end }}
{{ else }}
-<p><img src="/d/{{ .XID }}" loading=lazy title="{{ .Desc }}" alt="{{ .Desc }}">
+<p><img src="/d/{{ .XID }}" title="{{ .Desc }}" alt="{{ .Desc }}" loading="lazy">
{{ end }}
{{ end }}
{{ else }}
@@ 82,7 82,7 @@ in reply to: <a href="{{ .RID }}" rel=no
{{ if eq .Media "video/mp4" }}
<p><video controls src="{{ .URL }}">{{ .Name }}</video>
{{ else }}
-<p><img src="{{ .URL }}" title="{{ .Desc }}" alt="{{ .Desc }}">
+<p><img src="{{ .URL }}" title="{{ .Desc }}" alt="{{ .Desc }}" loading="lazy">
{{ end }}
{{ end }}
{{ end }}
M views/honkers.html +1 -1
@@ 27,7 27,7 @@
{{ range .Honkers }}
<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
@@ 52,3 52,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 +189 -9
@@ 17,8 17,10 @@ package main
import (
"bytes"
+ "crypto/sha256"
"crypto/sha512"
"database/sql"
+ "encoding/hex"
"fmt"
"html/template"
"io"
@@ 2337,11 2339,119 @@ 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
+}
+
+var avacache = cache.New(cache.Options{Filler: func(key avaKey) (string, bool) {
+ j, err := GetJunkFast(key.uid, key.url)
+ if err != nil {
+ dlog.Println("avatating: getting junk:", err)
+ return "", false
+ }
+ pfpurl, _ := j.GetString("icon", "url")
+ res, err := http.Get(pfpurl)
+ if err != nil {
+ dlog.Println("avatating: getting pfp url:", err)
+ return "", false
+ }
+ if res.StatusCode != 200 {
+ dlog.Printf("avatating: %s: not ok: %d", key.url, res.StatusCode)
+ return "", false
+ }
+ defer res.Body.Close()
+
+ data, err := io.ReadAll(res.Body)
+ if err != nil {
+ dlog.Println("avatating: io.ReadAll(), whoops:", err)
+ return "", false
+ }
+
+ img, err := shrinkit(data)
+ if err != nil {
+ return "", false
+ }
+ data = img.Data
+ format := img.Format
+ desc := fmt.Sprintf("avatar for %s", key.url)
+ media := "image/" + format
+ if format == "jpeg" {
+ format = "jpg"
+ }
+ name := xfiltrate() + "." + format
+ _, xid, err := savefileandxid(name, desc, "", media, true, data)
+ if err != nil {
+ elog.Printf("avatate: unable to save image: %s", err)
+ return "", false
+ }
+ return xid, true
+}, Reducer: func(key avaKey) string {
+ return key.url
+}, Duration: 24 * 7 * time.Hour})
+
+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, '/')
@@ 2360,16 2470,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(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) {
@@ 2425,23 2594,32 @@ func servememe(w http.ResponseWriter, r
http.ServeFile(w, r, dataDir+"/memes/"+meme)
}
-func servefile(w http.ResponseWriter, r *http.Request) {
- xid := mux.Vars(r)["xid"]
+func reallyservefile(xid string, w http.ResponseWriter) error {
var media string
var data []byte
row := stmtGetFileData.QueryRow(xid)
err := row.Scan(&media, &data)
if err != nil {
elog.Printf("error loading file: %s", err)
- http.NotFound(w, r)
- return
+ return err
}
- w.Header().Set("Content-Type", media)
- w.Header().Set("X-Content-Type-Options", "nosniff")
- w.Header().Set("Cache-Control", "max-age="+somedays())
+
+ if !develMode {
+ w.Header().Set("Content-Type", media)
+ w.Header().Set("X-Content-Type-Options", "nosniff")
+ w.Header().Set("Cache-Control", "max-age="+somedays())
+ }
w.Write(data)
+ return nil
}
+func servefile(w http.ResponseWriter, r *http.Request) {
+ xid := mux.Vars(r)["xid"]
+ err := reallyservefile(xid, w)
+ if err != nil {
+ http.NotFound(w, r)
+ }
+}
func nomoroboto(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "User-agent: *\n")
io.WriteString(w, "Disallow: /a\n")
@@ 2838,6 3016,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)