Compare commits

..

52 Commits

Author SHA1 Message Date
Dirk Nederveen fb41e86d8b
Merge branch 'main' into docker 2023-09-14 20:45:22 +02:00
Ted Unangst 58ecc76b90 toggle menu 2023-09-13 21:00:53 -04:00
Ted Unangst 386dc6f798 fix slow public queries with a special index just for you 2023-09-11 21:34:05 -04:00
Ted Unangst dbbd4468fa don't think i want to duplicate honks in threads yet 2023-09-11 18:54:05 -04:00
Ted Unangst c750122b7f need to cache front page internally to avoid herding 2023-09-11 18:52:21 -04:00
Ted Unangst 82c7e22193 Added tag v1.1.1 for changeset 135cdbfa6d7d 2023-08-31 16:40:18 -04:00
Ted Unangst 850ec873a8 fix the help stylesheet, from horia 2023-08-31 16:40:14 -04:00
Ted Unangst 90bef0b0b9 Added tag v1.1.0 for changeset 36c2a2746133 2023-08-31 03:10:45 -04:00
Ted Unangst 81bb8099ae fix help makefile target 2023-08-31 03:09:59 -04:00
Ted Unangst 1a00ca5b43 1.1 notes 2023-08-31 03:05:55 -04:00
Ted Unangst 281570fe6b backup code hasn't been well maintained 2023-08-31 02:57:49 -04:00
Ted Unangst ae3843690f this is web code 2023-08-31 02:48:07 -04:00
Ted Unangst 0a66a0e13a go fix 2023-08-31 00:09:38 -04:00
Ted Unangst b60e2e6156 don't leak <p> into summary, seen by eta 2023-08-30 19:12:12 -04:00
Ted Unangst 99aed03a36 smooth scroll 2023-08-30 03:03:10 -04:00
Ted Unangst 8380a32139 big resigh. need to resign after redirect. 2023-08-29 00:18:14 -04:00
Ted Unangst 7dc99cec9b csp requires classes not styles 2023-08-28 22:56:19 -04:00
Ted Unangst 1a1c56200c m and escape to open close menu. and set focus on actions after scroll. 2023-08-28 22:01:50 -04:00
Ted Unangst 76da4515bc update webs 2023-08-28 17:42:21 -04:00
Ted Unangst a6bc763507 negate operator for more queries 2023-08-28 17:41:56 -04:00
Ted Unangst 15572f2e21 better export/import. support markdown source now. 2023-08-28 16:26:33 -04:00
Ted Unangst a4817e149b consistent newlines in errx 2023-08-28 16:08:29 -04:00
Ted Unangst aad5243bb1 ignore alt and ctrl in hotkey 2023-08-28 15:00:18 -04:00
Ted Unangst b539f292a6 ignore help files 2023-08-28 02:10:23 -04:00
Ted Unangst cc3976daa6 command line errors to stderr and exit(1), like zev 2023-08-28 01:21:44 -04:00
Ted Unangst d69156f96e configurable honkwindow 2023-08-28 01:12:53 -04:00
Ted Unangst b360a01719 factor out 7 day honkwindow, from zev 2023-08-28 01:10:27 -04:00
Ted Unangst 3bbd1ad2b3 make target for help 2023-08-28 01:06:39 -04:00
Ted Unangst a4be8b9e02 note the hotkeys 2023-08-27 17:06:11 -04:00
Ted Unangst 0dd17d0038 a bigger shrinker for uploads 2023-08-27 15:25:58 -04:00
Ted Unangst 1b36e72cc1 need to allow images in markdown 2023-08-26 19:47:45 -04:00
Ted Unangst 314949d045 the zalgo filter was accidentally eating newlines too 2023-08-26 19:03:11 -04:00
Ted Unangst db63bd3fc3 some fixes for attachments and edits and previews 2023-08-26 18:09:21 -04:00
Ted Unangst 86738e30b8 if develmode, relative links for emoji 2023-08-26 17:42:14 -04:00
Ted Unangst 0ba146c00c hotkey / to focus search 2023-08-26 14:56:28 -04:00
Ted Unangst 2c5a051376 some hotkeys from zev 2023-08-26 14:39:09 -04:00
Ted Unangst 17f2f729ee start the changelog 2023-08-25 01:47:20 -04:00
Ted Unangst 6f5bf49fca allow uploading multiple files (but beware, it gets weird fast) 2023-08-25 01:34:48 -04:00
Ted Unangst bddd8554a2 rely less on id for form updates 2023-08-25 01:12:26 -04:00
Ted Unangst dbff9a6151 at least use servername for title, until we fill in better 2023-08-25 00:45:41 -04:00
Ted Unangst 4f0d15ebb5 i think maybe we want the thread sorted the other way actually? 2023-08-25 00:33:16 -04:00
Ted Unangst 634d74d139 a very plain text accessor that's helpful for debugging 2023-08-25 00:30:19 -04:00
Ted Unangst 93a79455d2 don't reset audience for updates 2023-08-24 13:09:41 -04:00
Ted Unangst f7cd268fe2 add some logging for unexpected activities 2023-08-24 11:54:10 -04:00
Ted Unangst 57e34c344d run analyze as final upgrade step 2023-08-23 23:34:14 -04:00
Ted Unangst f4c4c8a917 m 2023-08-19 21:16:33 -04:00
Ted Unangst 773eb649a1 solve the problem of slow requests running in parallel 2023-08-19 21:16:05 -04:00
Ted Unangst 9658fb8605 misc.js is a view dir file 2023-08-19 01:12:50 -04:00
Ted Unangst 20699112a0 matching import for the honk export. roughly roundtrips now. 2023-08-18 20:53:42 -04:00
Ted Unangst 842816f1bf also export the inbox 2023-08-18 14:32:27 -04:00
Ted Unangst 1910279c6b start working on an export command 2023-08-18 11:51:28 -04:00
Ted Unangst 0e4a87c523 turns out the buildsetting stuff was added in go 1.18 2023-08-13 23:54:24 -04:00
30 changed files with 641 additions and 172 deletions

View File

@ -7,3 +7,11 @@ memes
emus
honk
violations.json
docs/activitypub.7.html
docs/hfcs.1.html
docs/honk.1.html
docs/honk.3.html
docs/honk.5.html
docs/honk.8.html
docs/intro.1.html
docs/vim.3.html

View File

@ -39,3 +39,5 @@ bc1bcfb9c0cc86b3c63325b07e13a36b9d4500f0 v0.9.7
4b8cf31560b7d1e1696af109b158766c4ce823ab v0.9.9
d7c3a01e7aaef67c40920bbc4e8507350fc33e31 v0.9.91
b1e7ac92a58a7183310b1a5cca8222d65f242d81 v1.0.0
36c2a2746133f4b5b31103c0b4232554d2b15a5d v1.1.0
135cdbfa6d7d1a9b1b436cb57d9837a943d83227 v1.1.1

View File

@ -7,6 +7,11 @@ honk: .preflightcheck schema.sql *.go go.mod
.preflightcheck: preflight.sh
@sh ./preflight.sh
help:
for m in docs/*.[13578] ; do \
mandoc -T html -O style=mandoc.css,man=%N.%S.html $$m | sed -E 's/<a class="Lk" href="([[:alnum:]._-]*)">/<img src="\1"><br>/g' > $$m.html ; \
done
clean:
rm -f honk

2
README
View File

@ -20,7 +20,7 @@ This does not imply the goal is to be what you want.
## build
It should be sufficient to type make after unpacking a release.
You'll need a go compiler version 1.16 or later. And libsqlite3.
You'll need a go compiler version 1.18 or later. And libsqlite3.
Even on a fast machine, building from source can take several seconds.

View File

@ -59,12 +59,14 @@ func friendorfoe(ct string) bool {
return false
}
var develClient = &http.Client{
Transport: &http.Transport{
var honkClient = http.Client{}
func gogglesDoNothing() {
honkClient.Transport = &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
}
func PostJunk(keyname string, key httpsig.PrivateKey, url string, j junk.Junk) error {
@ -72,10 +74,6 @@ func PostJunk(keyname string, key httpsig.PrivateKey, url string, j junk.Junk) e
}
func PostMsg(keyname string, key httpsig.PrivateKey, url string, msg []byte) error {
client := http.DefaultClient
if develMode {
client = develClient
}
req, err := http.NewRequest("POST", url, bytes.NewReader(msg))
if err != nil {
return err
@ -86,7 +84,7 @@ func PostMsg(keyname string, key httpsig.PrivateKey, url string, msg []byte) err
ctx, cancel := context.WithTimeout(context.Background(), 2*slowTimeout*time.Second)
defer cancel()
req = req.WithContext(ctx)
resp, err := client.Do(req)
resp, err := honkClient.Do(req)
if err != nil {
return err
}
@ -130,13 +128,10 @@ func GetJunkHardMode(userid int64, url string) (junk.Junk, error) {
var flightdeck = gate.NewSerializer()
var signGets = true
func GetJunkTimeout(userid int64, url string, timeout time.Duration) (junk.Junk, error) {
if rejectorigin(userid, url, false) {
return nil, fmt.Errorf("rejected origin: %s", url)
}
client := http.DefaultClient
sign := func(req *http.Request) error {
var ki *KeyInfo
ok := ziggies.Get(userid, &ki)
@ -146,9 +141,18 @@ func GetJunkTimeout(userid int64, url string, timeout time.Duration) (junk.Junk,
return nil
}
if develMode {
client = develClient
sign = nil
}
client := honkClient
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
if len(via) >= 5 {
return fmt.Errorf("stopped after 5 redirects")
}
if sign != nil {
sign(req)
}
return nil
}
fn := func() (interface{}, error) {
at := theonetruename
if strings.Contains(url, ".well-known/webfinger?resource") {
@ -158,7 +162,7 @@ func GetJunkTimeout(userid int64, url string, timeout time.Duration) (junk.Junk,
Accept: at,
Agent: "honksnonk/5.0; " + serverName,
Timeout: timeout,
Client: client,
Client: &client,
Fixup: sign,
})
return j, err
@ -173,10 +177,6 @@ func GetJunkTimeout(userid int64, url string, timeout time.Duration) (junk.Junk,
}
func fetchsome(url string) ([]byte, error) {
client := http.DefaultClient
if develMode {
client = develClient
}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
ilog.Printf("error fetching %s: %s", url, err)
@ -186,7 +186,7 @@ func fetchsome(url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()
req = req.WithContext(ctx)
resp, err := client.Do(req)
resp, err := honkClient.Do(req)
if err != nil {
ilog.Printf("error fetching %s: %s", url, err)
return nil, err

View File

@ -83,6 +83,31 @@ func imageFromSVG(data []byte) (*image.Image, error) {
return svg, nil
}
func bigshrink(data []byte) (*image.Image, error) {
if isSVG(data) {
return imageFromSVG(data)
}
cl, err := rpc.Dial("unix", backendSockname())
if err != nil {
return nil, err
}
defer cl.Close()
var res ShrinkerResult
err = cl.Call("Shrinker.Shrink", &ShrinkerArgs{
Buf: data,
Params: image.Params{
LimitSize: 14200 * 4200,
MaxWidth: 2600,
MaxHeight: 2048,
MaxSize: 768 * 1024,
},
}, &res)
if err != nil {
return nil, err
}
return res.Image, nil
}
func shrinkit(data []byte) (*image.Image, error) {
if isSVG(data) {
return imageFromSVG(data)

View File

@ -35,6 +35,7 @@ func svalbard(dirname string) {
if err != nil {
elog.Fatalf("can't open backup database")
}
_, err = backup.Exec("PRAGMA journal_mode=WAL")
for _, line := range strings.Split(sqlSchema, ";") {
_, err = backup.Exec(line)
if err != nil {
@ -76,16 +77,16 @@ func svalbard(dirname string) {
honkids := make(map[int64]bool)
for c := range convoys {
rows = qordie(orig, "select honkid, userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker, flags from honks where convoy = ?", c)
rows = qordie(orig, "select honkid, userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker, flags, plain from honks where convoy = ?", c)
for rows.Next() {
var honkid, userid int64
var what, honker, xid, rid, dt, url, audience, noise, convoy string
var what, honker, xid, rid, dt, url, audience, noise, convoy, plain string
var whofore int64
var format, precis, oonker string
var flags int64
scanordie(rows, &honkid, &userid, &what, &honker, &xid, &rid, &dt, &url, &audience, &noise, &convoy, &whofore, &format, &precis, &oonker, &flags)
scanordie(rows, &honkid, &userid, &what, &honker, &xid, &rid, &dt, &url, &audience, &noise, &convoy, &whofore, &format, &precis, &oonker, &flags, &plain)
honkids[honkid] = true
doordie(tx, "insert into honks (honkid, userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker, flags) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", honkid, userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker, flags)
doordie(tx, "insert into honks (honkid, userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker, flags, plain) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", honkid, userid, what, honker, xid, rid, dt, url, audience, noise, convoy, whofore, format, precis, oonker, flags, plain)
}
rows.Close()
}
@ -169,6 +170,7 @@ func svalbard(dirname string) {
if err != nil {
elog.Fatalf("can't open backup blob database")
}
_, err = blob.Exec("PRAGMA journal_mode=WAL")
doordie(blob, "create table filedata (xid text, media text, hash text, content blob)")
doordie(blob, "create index idx_filexid on filedata(xid)")
doordie(blob, "create index idx_filehash on filedata(hash)")

View File

@ -36,6 +36,8 @@ import (
"humungus.tedunangst.com/r/webs/mz"
)
var honkwindow time.Duration = 7
//go:embed schema.sql
var sqlSchema string
@ -187,7 +189,7 @@ func getbonk(userid int64, xid string) *Honk {
}
func getpublichonks() []*Honk {
dt := time.Now().Add(-7 * 24 * time.Hour).UTC().Format(dbtimeformat)
dt := time.Now().Add(-honkwindow).UTC().Format(dbtimeformat)
rows, err := stmtPublicHonks.Query(dt, 100)
return getsomehonks(rows, err)
}
@ -223,7 +225,7 @@ func geteventhonks(userid int64) []*Honk {
return honks
}
func gethonksbyuser(name string, includeprivate bool, wanted int64) []*Honk {
dt := time.Now().Add(-7 * 24 * time.Hour).UTC().Format(dbtimeformat)
dt := time.Now().Add(-honkwindow).UTC().Format(dbtimeformat)
limit := 50
whofore := 2
if includeprivate {
@ -233,19 +235,19 @@ func gethonksbyuser(name string, includeprivate bool, wanted int64) []*Honk {
return getsomehonks(rows, err)
}
func gethonksforuser(userid int64, wanted int64) []*Honk {
dt := time.Now().Add(-7 * 24 * time.Hour).UTC().Format(dbtimeformat)
dt := time.Now().Add(-honkwindow).UTC().Format(dbtimeformat)
rows, err := stmtHonksForUser.Query(wanted, userid, dt, userid, userid)
return getsomehonks(rows, err)
}
func gethonksforuserfirstclass(userid int64, wanted int64) []*Honk {
dt := time.Now().Add(-7 * 24 * time.Hour).UTC().Format(dbtimeformat)
dt := time.Now().Add(-honkwindow).UTC().Format(dbtimeformat)
rows, err := stmtHonksForUserFirstClass.Query(wanted, userid, dt, userid, userid)
return getsomehonks(rows, err)
}
func gethonksforme(userid int64, wanted int64) []*Honk {
dt := time.Now().Add(-7 * 24 * time.Hour).UTC().Format(dbtimeformat)
rows, err := stmtHonksForMe.Query(wanted, userid, dt, userid)
dt := time.Now().Add(-honkwindow).UTC().Format(dbtimeformat)
rows, err := stmtHonksForMe.Query(wanted, userid, dt, userid, 250)
return getsomehonks(rows, err)
}
func gethonksfromlongago(userid int64, wanted int64) []*Honk {
@ -305,11 +307,11 @@ func gethonksbysearch(userid int64, q string, wanted int64) []*Honk {
continue
}
if t == "@me" {
queries = append(queries, "whofore = 1")
queries = append(queries, negate+"whofore = 1")
continue
}
if t == "@self" {
queries = append(queries, "(whofore = 2 or whofore = 3)")
queries = append(queries, negate+"(whofore = 2 or whofore = 3)")
continue
}
if strings.HasPrefix(t, "before:") {
@ -790,9 +792,19 @@ func loadchatter(userid int64) []*Chatter {
}
func (honk *Honk) Plain() string {
return honktoplain(honk, false)
}
func (honk *Honk) VeryPlain() string {
return honktoplain(honk, true)
}
func honktoplain(honk *Honk, very bool) string {
var plain []string
var filt htfilter.Filter
filt.WithLinks = true
if !very {
filt.WithLinks = true
}
if honk.Precis != "" {
t, _ := filt.TextOnly(honk.Precis)
plain = append(plain, t)
@ -1184,7 +1196,7 @@ func prepareStatements(db *sql.DB) {
myhonkers := " and honker in (select xid from honkers where userid = ? and (flavor = 'sub' or flavor = 'peep' or flavor = 'presub') and combos not like '% - %')"
stmtHonksForUser = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and dt > ?"+myhonkers+butnotthose+limit)
stmtHonksForUserFirstClass = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and dt > ? and (rid = '' or what = 'bonk')"+myhonkers+butnotthose+limit)
stmtHonksForMe = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and dt > ? and whofore = 1"+butnotthose+limit)
stmtHonksForMe = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and dt > ? and whofore = 1"+butnotthose+smalllimit)
stmtHonksFromLongAgo = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and dt > ? and dt < ? and whofore = 2"+butnotthose+limit)
stmtHonksISaved = preparetodie(db, selecthonks+"where honks.honkid > ? and honks.userid = ? and flags & 4 order by honks.honkid desc")
stmtHonksByHonker = preparetodie(db, selecthonks+"join honkers on (honkers.xid = honks.honker or honkers.xid = honks.oonker) where honks.honkid > ? and honks.userid = ? and honkers.name = ?"+butnotthose+limit)

View File

@ -118,8 +118,9 @@ func deliverate(userid int64, rcpt string, msg []byte) {
var garage = gate.NewLimiter(40)
func deliveration(doover Doover) {
garage.Start()
defer garage.Finish()
rcpt := doover.Rcpt
garage.StartKey(rcpt)
defer garage.FinishKey(rcpt)
var ki *KeyInfo
ok := ziggies.Get(doover.Userid, &ki)
@ -128,7 +129,6 @@ func deliveration(doover Doover) {
return
}
var inbox string
rcpt := doover.Rcpt
// already did the box indirection
if rcpt[0] == '%' {
inbox = rcpt[1:]

View File

@ -1,5 +1,39 @@
changelog
### next
+ Finally fix slow public queries.
### 1.1.1 Required Refinement
+ Fix help file stylesheet link.
### 1.1.0 Eventual Enshittification
+ Fix backup command.
+ Fixes for markdown.
+ Allow bigger image uploads.
+ Some hotkeys for the web UI.
+ Upload multiple files (but beware).
+ Better page titles.
+ Refine thread sort.
+ Send updates to correct audience.
+ Run analyze to improve database performance.
+ Delivery performance improvements.
+ Export command to ActivityPub data. (And import.)
+ Note that we require go 1.18 now.
### 1.0.0 Happy Honker
+ A great big honk composition text box.

View File

@ -130,11 +130,28 @@ Replies higher in the tree are still received.
Please no.
.It Ic edit
Change it up.
Alas, Update activities do not federate reliably.
.Ss Refresh
Clicking the refresh button will load new honks, if any.
New honks will be subtly highlighted.
.El
.Ss Hotkeys
The following keyboard shortcuts may also be used to navigate.
.Bl -tag -width short
.It j
Scroll to next honk.
.It k
Scroll to previous honk.
.It r
Refresh.
.It s
Scroll down to oldest newest.
.It m
Open menu.
.It esc
Close menu.
.It /
Search.
.El
.Ss Honking
Refer to the
.Xr honk 5

View File

@ -41,7 +41,7 @@ proxy_set_header Host $http_host;
.Ss Build
Building
.Nm
requires a go compiler 1.16 and libsqlite.
requires a go compiler 1.18 and libsqlite.
On
.Ox
this is the go and sqlite3 packages.
@ -194,10 +194,13 @@ and templates are reloaded every request.
Data may be imported and converted from other services using the
.Ic import
command.
Currently supports Mastodon, Twitter, and Instagram exported data.
Currently supports Honk, Mastodon, Twitter, and Instagram exported data.
Posts are imported and backdated to appear as old honks.
The Mastodon following list is imported, but must be refollowed.
.Pp
To prepare a Honk data archive, extract the export.zip file.
.Dl ./honk import username honk source-directory
.Pp
To prepare a Mastodon data archive, extract the archive-longhash.tar.gz file.
.Dl ./honk import username mastodon source-directory
.Pp
@ -208,6 +211,13 @@ and unzip any zip files contained within.
.Pp
To prepare an Instagram data archive, extract the igusername.zip file.
.Dl ./honk import username instagram source-directory
.Ss Export
User data may be exported to a zip archive using the
.Ic export
command.
This will export the user's outbox and inbox in ActvityPub json format,
along with associated media.
.Dl ./honk export username zipname
.Ss Advanced Options
Advanced configuration values may be set by running the
.Ic setconfig Ar key value

5
fun.go
View File

@ -330,6 +330,7 @@ func precipitate(honk *Honk) {
noise = noise[idx+1:]
}
var marker mz.Marker
marker.Short = true
honk.Precis = marker.Mark(strings.TrimSpace(honk.Precis))
honk.Noise = noise
}
@ -344,6 +345,7 @@ func translate(honk *Honk) {
var marker mz.Marker
marker.HashLinker = ontoreplacer
marker.AtLinker = attoreplacer
marker.AllowImages = true
noise = strings.TrimSpace(noise)
noise = marker.Mark(noise)
honk.Noise = noise
@ -432,6 +434,9 @@ var emucache = cache.New(cache.Options{Filler: func(ename string) (Emu, bool) {
continue
}
url := fmt.Sprintf("https://%s/emu/%s%s", serverName, fname, ext)
if develMode {
url = fmt.Sprintf("/emu/%s%s", fname, ext)
}
return Emu{ID: url, Name: ename, Type: "image/" + ext[1:]}, true
}
return Emu{Name: ename, ID: "", Type: "image/png"}, true

13
go.mod
View File

@ -1,13 +1,18 @@
module humungus.tedunangst.com/r/honk
go 1.16
go 1.18
require (
github.com/andybalholm/cascadia v1.3.1
github.com/gorilla/mux v1.8.0
github.com/mattn/go-runewidth v0.0.13
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
golang.org/x/crypto v0.12.0
golang.org/x/net v0.14.0
humungus.tedunangst.com/r/go-sqlite3 v1.1.3
humungus.tedunangst.com/r/webs v0.6.68
humungus.tedunangst.com/r/webs v0.7.9
)
require (
github.com/rivo/uniseg v0.2.0 // indirect
golang.org/x/image v0.11.0 // indirect
)

43
go.sum
View File

@ -6,24 +6,47 @@ github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 h1:LRtI4W37N+KFebI/qV0OFiLUv4GLOWeEW5hn/KEJvxE=
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
humungus.tedunangst.com/r/go-sqlite3 v1.1.3 h1:G2N4wzDS0NbuvrZtQJhh4F+3X+s7BF8b9ga8k38geUI=
humungus.tedunangst.com/r/go-sqlite3 v1.1.3/go.mod h1:FtEEmQM7U2Ey1TuEEOyY1BmphTZnmiEjPsNLEAkpf/M=
humungus.tedunangst.com/r/webs v0.6.68 h1:veKjASf1krPf4o3O7hMRsNvE4+Z6LzXVso/qMccZntk=
humungus.tedunangst.com/r/webs v0.6.68/go.mod h1:03R0N9BcT49HB4TDd1YmarpbiPvPzVDm74Mk4h1hYPc=
humungus.tedunangst.com/r/webs v0.7.9 h1:LC9o2F9joAcf4SxWaRFs5ZqXHSbzdfre9/9BY0gcM0w=
humungus.tedunangst.com/r/webs v0.7.9/go.mod h1:ylhqHSPI0Oi7b4nsnx5mSO7AjLXN7wFpEHayLfN/ugk=

179
import.go
View File

@ -16,6 +16,7 @@
package main
import (
"archive/zip"
"encoding/csv"
"encoding/json"
"fmt"
@ -27,12 +28,16 @@ import (
"sort"
"strings"
"time"
"humungus.tedunangst.com/r/webs/junk"
)
func importMain(username, flavor, source string) {
switch flavor {
case "mastodon":
importMastodon(username, source)
case "honk":
importHonk(username, source)
case "twitter":
importTwitter(username, source)
case "instagram":
@ -42,11 +47,17 @@ func importMain(username, flavor, source string) {
}
}
type TootObject struct {
type ActivityObject struct {
AttributedTo string
Summary string
Content string
Source struct {
MediaType string
Content string
}
InReplyTo string
Conversation string
Context string
Published time.Time
Tag []struct {
Type string
@ -60,10 +71,10 @@ type TootObject struct {
}
}
type PlainTootObject TootObject
type PlainActivityObject ActivityObject
func (obj *TootObject) UnmarshalJSON(b []byte) error {
p := (*PlainTootObject)(obj)
func (obj *ActivityObject) UnmarshalJSON(b []byte) error {
p := (*PlainActivityObject)(obj)
json.Unmarshal(b, p)
return nil
}
@ -74,8 +85,9 @@ func importMastodon(username, source string) {
elog.Fatal(err)
}
if _, err := os.Stat(source + "/outbox.json"); err == nil {
importMastotoots(user, source)
outbox := source + "/outbox.json"
if _, err := os.Stat(outbox); err == nil {
importActivities(user, outbox, source)
} else {
ilog.Printf("skipping outbox.json!")
}
@ -86,19 +98,33 @@ func importMastodon(username, source string) {
}
}
func importMastotoots(user *WhatAbout, source string) {
type Toot struct {
func importHonk(username, source string) {
user, err := butwhatabout(username)
if err != nil {
elog.Fatal(err)
}
outbox := source + "/outbox.json"
if _, err := os.Stat(outbox); err == nil {
importActivities(user, outbox, source)
} else {
ilog.Printf("skipping outbox.json!")
}
}
func importActivities(user *WhatAbout, filename, source string) {
type Activity struct {
Id string
Type string
To []string
To interface{}
Cc []string
Object TootObject
Object ActivityObject
}
var outbox struct {
OrderedItems []Toot
OrderedItems []Activity
}
ilog.Println("Importing honks...")
fd, err := os.Open(source + "/outbox.json")
fd, err := os.Open(filename)
if err != nil {
elog.Fatal(err)
}
@ -120,7 +146,11 @@ func importMastotoots(user *WhatAbout, source string) {
}
re_tootid := regexp.MustCompile("[^/]+$")
for _, item := range outbox.OrderedItems {
items := outbox.OrderedItems
for i, j := 0, len(items)-1; i < j; i, j = i+1, j-1 {
items[i], items[j] = items[j], items[i]
}
for _, item := range items {
toot := item
if toot.Type != "Create" {
continue
@ -133,6 +163,27 @@ func importMastotoots(user *WhatAbout, source string) {
if havetoot(xid) {
continue
}
convoy := toot.Object.Context
if convoy == "" {
convoy = toot.Object.Conversation
}
var audience []string
to, ok := toot.To.(string)
if ok {
audience = append(audience, to)
} else {
for _, t := range toot.To.([]interface{}) {
audience = append(audience, t.(string))
}
}
content := toot.Object.Content
format := "html"
if toot.Object.Source.MediaType == "text/markdown" {
content = toot.Object.Source.Content
format = "markdown"
}
audience = append(audience, toot.Cc...)
honk := Honk{
UserID: user.ID,
What: "honk",
@ -141,11 +192,11 @@ func importMastotoots(user *WhatAbout, source string) {
RID: toot.Object.InReplyTo,
Date: toot.Object.Published,
URL: xid,
Audience: append(toot.To, toot.Cc...),
Noise: toot.Object.Content,
Convoy: toot.Object.Conversation,
Audience: audience,
Noise: content,
Convoy: convoy,
Whofore: 2,
Format: "html",
Format: format,
Precis: toot.Object.Summary,
}
if !loudandproud(honk.Audience) {
@ -157,7 +208,7 @@ func importMastotoots(user *WhatAbout, source string) {
fname := fmt.Sprintf("%s/%s", source, att.Url)
data, err := ioutil.ReadFile(fname)
if err != nil {
elog.Printf("error reading media: %s", fname)
elog.Printf("error reading media for %s: %s", honk.XID, fname)
continue
}
u := xfiltrate()
@ -513,3 +564,95 @@ func importInstagram(username, source string) {
log.Printf("honk saved %v -> %v", xid, err)
}
}
func export(username, file string) {
user, err := butwhatabout(username)
if err != nil {
elog.Fatal(err)
}
fd, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
if err != nil {
elog.Fatal(err)
}
zd := zip.NewWriter(fd)
donks := make(map[string]bool)
{
w, err := zd.Create("outbox.json")
if err != nil {
elog.Fatal("error creating outbox.json", err)
}
var jonks []junk.Junk
rows, err := stmtUserHonks.Query(0, 3, user.Name, "0", 1234567)
honks := getsomehonks(rows, err)
for _, honk := range honks {
for _, donk := range honk.Donks {
donk.URL = "media/" + donk.XID
donks[donk.XID] = true
}
noise := honk.Noise
j, jo := jonkjonk(user, honk)
if honk.Format == "markdown" {
source := junk.New()
source["mediaType"] = "text/markdown"
source["content"] = noise
jo["source"] = source
}
jonks = append(jonks, j)
}
j := junk.New()
j["@context"] = itiswhatitis
j["id"] = user.URL + "/outbox"
j["attributedTo"] = user.URL
j["type"] = "OrderedCollection"
j["totalItems"] = len(jonks)
j["orderedItems"] = jonks
j.Write(w)
}
{
w, err := zd.Create("inbox.json")
if err != nil {
elog.Fatal("error creating inbox.json", err)
}
var jonks []junk.Junk
rows, err := stmtHonksForMe.Query(0, user.ID, "0", user.ID, 1234567)
honks := getsomehonks(rows, err)
for _, honk := range honks {
for _, donk := range honk.Donks {
donk.URL = "media/" + donk.XID
donks[donk.XID] = true
}
j, _ := jonkjonk(user, honk)
jonks = append(jonks, j)
}
j := junk.New()
j["@context"] = itiswhatitis
j["id"] = user.URL + "/inbox"
j["attributedTo"] = user.URL
j["type"] = "OrderedCollection"
j["totalItems"] = len(jonks)
j["orderedItems"] = jonks
j.Write(w)
}
zd.Create("media/")
for donk := range donks {
if donk == "" {
continue
}
var media string
var data []byte
w, err := zd.Create("media/" + donk)
if err != nil {
elog.Printf("error creating %s: %s", donk, err)
continue
}
row := stmtGetFileData.QueryRow(donk)
err = row.Scan(&media, &data)
if err != nil {
elog.Printf("error scanning file %s: %s", donk, err)
continue
}
w.Write(data)
}
zd.Close()
fd.Close()
}

73
main.go
View File

@ -64,6 +64,11 @@ func reexecArgs(cmd string) []string {
var elog, ilog, dlog *golog.Logger
func errx(msg string, args ...interface{}) {
fmt.Fprintf(os.Stderr, msg+"\n", args...)
os.Exit(1)
}
func main() {
flag.StringVar(&dataDir, "datadir", dataDir, "data directory")
flag.StringVar(&viewDir, "viewdir", viewDir, "view directory")
@ -110,21 +115,32 @@ func main() {
getconfig("usersep", &userSep)
getconfig("honksep", &honkSep)
getconfig("devel", &develMode)
if develMode {
gogglesDoNothing()
}
getconfig("fasttimeout", &fastTimeout)
getconfig("slowtimeout", &slowTimeout)
getconfig("signgets", &signGets)
getconfig("honkwindow", &honkwindow)
honkwindow *= 24 * time.Hour
prepareStatements(db)
switch cmd {
case "admin":
adminscreen()
case "import":
if len(args) != 4 {
elog.Fatal("import username mastodon|twitter srcdir")
errx("import username honk|mastodon|twitter srcdir")
}
importMain(args[1], args[2], args[3])
case "export":
if len(args) != 3 {
errx("export username destdir")
}
export(args[1], args[2])
case "devel":
if len(args) != 2 {
elog.Fatal("need an argument: devel (on|off)")
errx("need an argument: devel (on|off)")
}
switch args[1] {
case "on":
@ -132,11 +148,11 @@ func main() {
case "off":
setconfig("devel", 0)
default:
elog.Fatal("argument must be on or off")
errx("argument must be on or off")
}
case "setconfig":
if len(args) != 3 {
elog.Fatal("need an argument: setconfig key val")
errx("need an argument: setconfig key val")
}
var val interface{}
var err error
@ -148,66 +164,55 @@ func main() {
adduser()
case "deluser":
if len(args) < 2 {
fmt.Printf("usage: honk deluser username\n")
return
errx("usage: honk deluser username")
}
deluser(args[1])
case "chpass":
if len(args) < 2 {
fmt.Printf("usage: honk chpass username\n")
return
errx("usage: honk chpass username")
}
chpass(args[1])
case "follow":
if len(args) < 3 {
fmt.Printf("usage: honk follow username url\n")
return
errx("usage: honk follow username url")
}
user, err := butwhatabout(args[1])
if err != nil {
fmt.Printf("user not found\n")
return
errx("user %s not found", args[1])
}
var meta HonkerMeta
mj, _ := jsonify(&meta)
honkerid, err := savehonker(user, args[2], "", "presub", "", mj)
if err != nil {
fmt.Printf("had some trouble with that: %s\n", err)
return
errx("had some trouble with that: %s", err)
}
followyou(user, honkerid, true)
case "unfollow":
if len(args) < 3 {
fmt.Printf("usage: honk unfollow username url\n")
return
errx("usage: honk unfollow username url")
}
user, err := butwhatabout(args[1])
if err != nil {
fmt.Printf("user not found\n")
return
errx("user not found")
}
row := db.QueryRow("select honkerid from honkers where xid = ? and userid = ? and flavor in ('sub')", args[2], user.ID)
var honkerid int64
err = row.Scan(&honkerid)
if err != nil {
fmt.Printf("sorry couldn't find them\n")
return
errx("sorry couldn't find them")
}
unfollowyou(user, honkerid, true)
case "sendmsg":
if len(args) < 4 {
fmt.Printf("usage: honk send username filename rcpt\n")
return
errx("usage: honk send username filename rcpt")
}
user, err := butwhatabout(args[1])
if err != nil {
fmt.Printf("user not found\n")
return
errx("user %s not found", args[1])
}
data, err := os.ReadFile(args[2])
if err != nil {
fmt.Printf("can't read file\n")
return
errx("can't read file: %s", err)
}
deliverate(user.ID, args[3], data)
case "cleanup":
@ -218,29 +223,25 @@ func main() {
cleanupdb(arg)
case "unplug":
if len(args) < 2 {
fmt.Printf("usage: honk unplug servername\n")
return
errx("usage: honk unplug servername")
}
name := args[1]
unplugserver(name)
case "backup":
if len(args) < 2 {
fmt.Printf("usage: honk backup dirname\n")
return
errx("usage: honk backup dirname")
}
name := args[1]
svalbard(name)
case "ping":
if len(args) < 3 {
fmt.Printf("usage: honk ping (from username) (to username or url)\n")
return
errx("usage: honk ping (from username) (to username or url)")
}
name := args[1]
targ := args[2]
user, err := butwhatabout(name)
if err != nil {
elog.Printf("unknown user")
return
errx("unknown user %s", name)
}
ping(user, targ)
case "run":
@ -250,6 +251,6 @@ func main() {
case "test":
ElaborateUnitTests()
default:
elog.Fatal("unknown command")
errx("unknown command")
}
}

View File

@ -1,11 +1,11 @@
set -e
go version > /dev/null 2>&1 || (echo go 1.16+ is required && false)
go version > /dev/null 2>&1 || (echo go 1.18+ is required && false)
v=`go version | egrep -o "go1\.[^.]+"` || echo failed to identify go version
if [ "$v" \< "go1.16" ] ; then
if [ "$v" \< "go1.18" ] ; then
echo go version is too old: $v
echo go 1.16+ is required
echo go 1.18+ is required
false
fi

View File

@ -15,6 +15,7 @@ create index idx_honksxid on honks(xid);
create index idx_honksconvoy on honks(convoy);
create index idx_honkshonker on honks(honker);
create index idx_honksoonker on honks(oonker);
create index idx_honkswhotwo on honks(whofore) where whofore = 2;
create index idx_donkshonk on donks(honkid);
create index idx_donkschonk on donks(chonkid);
create index idx_honkerxid on honkers(xid);

View File

@ -29,6 +29,9 @@ func demoji(s string) string {
zw := false
for _, c := range s {
if c == '\n' {
continue
}
if runewidth.RuneWidth(c) == 0 {
zw = true
break

View File

@ -1,5 +1,4 @@
//go:build openbsd
// +build openbsd
//
// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>

View File

@ -23,7 +23,7 @@ import (
"humungus.tedunangst.com/r/webs/htfilter"
)
var myVersion = 45
var myVersion = 46 // idx whotwo
type dbexecer interface {
Exec(query string, args ...interface{}) (sql.Result, error)
@ -172,6 +172,11 @@ func upgradedb() {
tx = nil
fallthrough
case 45:
try("create index idx_honkswhotwo on honks(whofore) where whofore = 2")
setV(46)
fallthrough
case 46:
try("analyze")
default:
elog.Fatalf("can't upgrade unknown version %d", dbversion)

18
util.go
View File

@ -36,10 +36,8 @@ import (
"bufio"
"crypto/rand"
"crypto/rsa"
"crypto/sha512"
"database/sql"
"fmt"
"io/ioutil"
"net"
"os"
"os/signal"
@ -52,24 +50,8 @@ import (
"humungus.tedunangst.com/r/webs/login"
)
var savedassetparams = make(map[string]string)
var re_plainname = regexp.MustCompile("^[[:alnum:]_-]+$")
func getassetparam(file string) string {
if p, ok := savedassetparams[file]; ok {
return p
}
data, err := ioutil.ReadFile(file)
if err != nil {
return ""
}
hasher := sha512.New()
hasher.Write(data)
return fmt.Sprintf("?v=%.8x", hasher.Sum(nil))
}
var dbtimeformat = "2006-01-02 15:04:05"
var alreadyopendb *sql.DB

View File

@ -5,10 +5,10 @@
<p>
<table class="font08em">
<tbody>
<tr><td>version:<td class="textright">{{ .HonkVersion }}
<tr><td>memory:<td class="textright">{{ printf "%.02f" .Sensors.Memory }}MB
<tr><td>uptime:<td class="textright">{{ printf "%.02f" .Sensors.Uptime }}s
<tr><td>cputime:<td class="textright">{{ printf "%.02f" .Sensors.CPU }}s
<tr><td>version:<td class="text-right">{{ .HonkVersion }}
<tr><td>memory:<td class="text-right">{{ printf "%.02f" .Sensors.Memory }}MB
<tr><td>uptime:<td class="text-right">{{ printf "%.02f" .Sensors.Uptime }}s
<tr><td>cputime:<td class="text-right">{{ printf "%.02f" .Sensors.CPU }}s
</table>
<p>
</div>

View File

@ -1,7 +1,7 @@
<!doctype html>
<html>
<head>
<title>honk</title>
<title>{{ or .Title .ServerName }}</title>
<link href="/style.css{{ .StyleParam }}" rel="stylesheet">
{{ if .LocalStyleParam }}
<link href="/local.css{{ .LocalStyleParam }}" rel="stylesheet">
@ -52,7 +52,7 @@
<li><a href="/help/honk.1.html">help</a>
<li>
<form action="/q" method="GET">
<input type="text" name="q" autocomplete=off size=10 placeholder="search">
<input type="text" name="q" autocomplete=off size=10 id="searchbox" placeholder="search">
</form>
</ul>
</details>

View File

@ -9,8 +9,7 @@
<details>
<summary>more options</summary>
<p>
<label class=button id="donker">attach: <input type="file" name="donk"><span>{{ .SavedFile }}</span></label>
<input type="hidden" id="saveddonkxid" name="donkxid" value="{{ .SavedFile }}">
<label class=button id="donker">attach: <input type="file" multiple name="donk"><span>{{ .SavedFile }}</span></label><input type="hidden" id="saveddonkxid" name="donkxid" value="{{ .SavedFile }}">
<p id="donkdescriptor"><label for=donkdesc>description:</label><br>
<input type="text" name="donkdesc" value="{{ .DonkDesc }}" autocomplete=off>
{{ with .SavedPlace }}

View File

@ -18,8 +18,8 @@
</div>
{{ if and .HonkCSRF (not .IsPreview) }}
<div class="info" id="refreshbox">
<p><button class="refresh">refresh</button><span></span>
<button class="scrolldown">scroll down</button>
<p><button id="honkrefresher" class="refresh">refresh</button><span></span>
<button id="newerscroller" class="scrolldown">scroll down</button>
</div>
{{ end }}
<div id="honksonpage">

View File

@ -82,7 +82,7 @@ var lehonkbutton = document.getElementById("honkingtime")
function oldestnewest(btn) {
var els = document.getElementsByClassName("glow")
if (els.length) {
els[els.length-1].scrollIntoView()
els[els.length-1].scrollIntoView({ behavior: "smooth" })
}
}
function removeglow() {
@ -384,13 +384,13 @@ function hideelement(el) {
if (!el) return
el.style.display = "none"
}
function updatedonker() {
var el = document.getElementById("donker")
function updatedonker(ev) {
var el = ev.target.parentElement
el.children[1].textContent = el.children[0].value.slice(-20)
el = document.getElementById("donkdescriptor")
el.style.display = ""
el = document.getElementById("saveddonkxid")
el = el.nextSibling
el.value = ""
el = el.parentElement.nextSibling
el.style.display = ""
}
var checkinprec = 100.0
var gpsoptions = {
@ -416,6 +416,76 @@ function fillcheckin() {
}, gpsoptions)
}
}
function scrollnexthonk() {
var honks = document.getElementsByClassName("honk");
for (var i = 0; i < honks.length; i++) {
var h = honks[i];
var b = h.getBoundingClientRect();
if (b.top > 1.0) {
h.scrollIntoView()
var a = h.querySelector(".actions summary")
if (a) a.focus({ preventScroll: true })
break
}
}
}
function scrollprevioushonk() {
var honks = document.getElementsByClassName("honk");
for (var i = 1; i < honks.length; i++) {
var b = honks[i].getBoundingClientRect();
if (b.top > -1.0) {
honks[i-1].scrollIntoView()
var a = honks[i-1].querySelector(".actions summary")
if (a) a.focus({ preventScroll: true })
break
}
}
}
function hotkey(e) {
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement)
return
if (e.ctrlKey || e.altKey)
return
switch (e.code) {
case "KeyR":
refreshhonks(document.getElementById("honkrefresher"));
break;
case "KeyS":
oldestnewest(document.getElementById("newerscroller"));
break;
case "KeyJ":
scrollnexthonk();
break;
case "KeyK":
scrollprevioushonk();
break;
case "KeyM":
var menu = document.getElementById("topmenu")
if (!menu.open) {
menu.open = true
menu.querySelector("a").focus()
} else {
menu.open = false
}
break
case "Escape":
var menu = document.getElementById("topmenu")
menu.open = false
break
case "Slash":
document.getElementById("topmenu").open = true
document.getElementById("searchbox").focus()
e.preventDefault()
break
}
}
document.addEventListener("keydown", hotkey)
function addemu(elem) {
const data = elem.alt
const box = document.getElementById("honknoise");

View File

@ -389,9 +389,15 @@ li.details {
display: none;
}
.textright {
.text-left {
text-align: left;
}
.text-right {
text-align: right;
}
.text-center {
text-align: center;
}
.font08em {
font-size: 0.8em;

162
web.go
View File

@ -17,11 +17,13 @@ package main
import (
"bytes"
"crypto/sha512"
"database/sql"
"fmt"
"html/template"
"io"
notrand "math/rand"
"mime/multipart"
"net/http"
"net/url"
"os"
@ -36,6 +38,7 @@ import (
"github.com/gorilla/mux"
"humungus.tedunangst.com/r/webs/cache"
"humungus.tedunangst.com/r/webs/gencache"
"humungus.tedunangst.com/r/webs/httpsig"
"humungus.tedunangst.com/r/webs/junk"
"humungus.tedunangst.com/r/webs/login"
@ -81,11 +84,14 @@ func getInfo(r *http.Request) map[string]interface{} {
templinfo["StyleParam"] = getassetparam(viewDir + "/views/style.css")
templinfo["LocalStyleParam"] = getassetparam(dataDir + "/views/local.css")
templinfo["JSParam"] = getassetparam(viewDir + "/views/honkpage.js")
templinfo["MiscJSParam"] = getassetparam(viewDir + "/views/misc.js")
templinfo["LocalJSParam"] = getassetparam(dataDir + "/views/local.js")
templinfo["MiscJSParam"] = getassetparam(dataDir + "/views/misc.js")
templinfo["ServerName"] = serverName
templinfo["IconName"] = iconName
templinfo["UserSep"] = userSep
if r == nil {
return templinfo
}
if u := login.GetUserInfo(r); u != nil {
templinfo["UserInfo"], _ = butwhatabout(u.Username)
templinfo["UserStyle"] = getuserstyle(u)
@ -96,9 +102,50 @@ func getInfo(r *http.Request) map[string]interface{} {
return templinfo
}
var oldnews = gencache.New(gencache.Options[string, []byte]{
Fill: func(url string) ([]byte, bool) {
templinfo := getInfo(nil)
var honks []*Honk
var userid int64 = -1
templinfo["ServerMessage"] = serverMsg
switch url {
case "/events":
honks = geteventhonks(userid)
templinfo["ServerMessage"] = "some recent and upcoming events"
default:
templinfo["ShowRSS"] = true
honks = getpublichonks()
}
reverbolate(userid, honks)
templinfo["Honks"] = honks
templinfo["MapLink"] = getmaplink(nil)
var buf bytes.Buffer
err := readviews.Execute(&buf, "honkpage.html", templinfo)
if err != nil {
elog.Print(err)
}
return buf.Bytes(), true
},
Duration: 1 * time.Minute,
})
func lonelypage(w http.ResponseWriter, r *http.Request) {
page, _ := oldnews.Get(r.URL.Path)
if !develMode {
w.Header().Set("Cache-Control", "max-age=60")
}
w.Write(page)
}
func homepage(w http.ResponseWriter, r *http.Request) {
templinfo := getInfo(r)
u := login.GetUserInfo(r)
if u == nil {
lonelypage(w, r)
return
}
templinfo := getInfo(r)
var honks []*Honk
var userid int64 = -1
@ -449,7 +496,20 @@ func inbox(w http.ResponseWriter, r *http.Request) {
addreaction(user, obj, who, content)
}
default:
go xonksaver(user, j, origin)
go saveandcheck(user, j, origin)
}
}
func saveandcheck(user *WhatAbout, j junk.Junk, origin string) {
xonk := xonksaver(user, j, origin)
if xonk == nil {
return
}
if sname := shortname(user.ID, xonk.Honker); sname == "" {
dlog.Printf("received unexpected activity from %s", xonk.Honker)
if xonk.Whofore == 0 {
dlog.Printf("it's not even for me!")
}
}
}
@ -1065,9 +1125,16 @@ func threadsort(honks []*Honk) []*Honk {
}
p.Style += fmt.Sprintf(" level%d", level)
childs := kids[p.XID]
sort.SliceStable(childs, func(i, j int) bool {
return sameperson(childs[i], p) && !sameperson(childs[j], p)
})
if false {
sort.SliceStable(childs, func(i, j int) bool {
return sameperson(childs[i], p) && !sameperson(childs[j], p)
})
}
if true {
sort.SliceStable(childs, func(i, j int) bool {
return !sameperson(childs[i], p) && sameperson(childs[j], p)
})
}
for _, h := range childs {
if !done[h] {
done[h] = true
@ -1177,10 +1244,10 @@ func showonehonk(w http.ResponseWriter, r *http.Request) {
//reversehonks(rawhonks)
rawhonks = threadsort(rawhonks)
var honks []*Honk
for _, h := range rawhonks {
for i, h := range rawhonks {
if h.XID == xid {
templinfo["Honkology"] = honkology(h)
if len(honks) != 0 {
if i > 0 {
h.Style += " glow"
}
}
@ -1548,11 +1615,15 @@ func edithonkpage(w http.ResponseWriter, r *http.Request) {
templinfo["Duration"] = tm.Duration
}
}
templinfo["ServerMessage"] = "honk edit 2"
templinfo["ServerMessage"] = "honk edit"
templinfo["IsPreview"] = true
templinfo["UpdateXID"] = honk.XID
if len(honk.Donks) > 0 {
templinfo["SavedFile"] = honk.Donks[0].XID
var savedfiles []string
for _, d := range honk.Donks {
savedfiles = append(savedfiles, fmt.Sprintf("%s:%d", d.XID, d.FileID))
}
templinfo["SavedFile"] = strings.Join(savedfiles, ",")
}
err := readviews.Execute(w, "honkpage.html", templinfo)
if err != nil {
@ -1592,11 +1663,26 @@ func canedithonk(user *WhatAbout, honk *Honk) bool {
return true
}
func submitdonk(w http.ResponseWriter, r *http.Request) (*Donk, error) {
func submitdonk(w http.ResponseWriter, r *http.Request) ([]*Donk, error) {
if !strings.HasPrefix(strings.ToLower(r.Header.Get("Content-Type")), "multipart/form-data") {
return nil, nil
}
file, filehdr, err := r.FormFile("donk")
var donks []*Donk
for i, hdr := range r.MultipartForm.File["donk"] {
if i > 16 {
break
}
donk, err := formtodonk(w, r, hdr)
if err != nil {
return nil, err
}
donks = append(donks, donk)
}
return donks, nil
}
func formtodonk(w http.ResponseWriter, r *http.Request, filehdr *multipart.FileHeader) (*Donk, error) {
file, err := filehdr.Open()
if err != nil {
if err == http.ErrMissingFile {
return nil, nil
@ -1610,7 +1696,7 @@ func submitdonk(w http.ResponseWriter, r *http.Request) (*Donk, error) {
file.Close()
data := buf.Bytes()
var media, name string
img, err := shrinkit(data)
img, err := bigshrink(data)
if err == nil {
data = img.Data
format := img.Format
@ -1770,7 +1856,7 @@ func submithonk(w http.ResponseWriter, r *http.Request) *Honk {
honk.Precis = "re: " + honk.Precis
}
}
} else {
} else if updatexid == "" {
honk.Audience = []string{thewholeworld}
}
if honk.Noise != "" && honk.Noise[0] == '@' {
@ -1792,18 +1878,26 @@ func submithonk(w http.ResponseWriter, r *http.Request) *Honk {
honk.Public = loudandproud(honk.Audience)
honk.Convoy = convoy
donkxid := r.FormValue("donkxid")
donkxid := strings.Join(r.Form["donkxid"], ",")
if donkxid == "" {
d, err := submitdonk(w, r)
donks, err := submitdonk(w, r)
if err != nil && err != http.ErrMissingFile {
return nil
}
if d != nil {
honk.Donks = append(honk.Donks, d)
donkxid = fmt.Sprintf("%s:%d", d.XID, d.FileID)
if len(donks) > 0 {
honk.Donks = append(honk.Donks, donks...)
var xids []string
for _, d := range honk.Donks {
xids = append(xids, fmt.Sprintf("%s:%d", d.XID, d.FileID))
}
donkxid = strings.Join(xids, ",")
}
} else {
for _, xid := range r.Form["donkxid"] {
xids := strings.Split(donkxid, ",")
for i, xid := range xids {
if i > 16 {
break
}
p := strings.Split(xid, ":")
xid = p[0]
url := fmt.Sprintf("https://%s/d/%s", serverName, xid)
@ -1972,12 +2066,12 @@ func submitchonk(w http.ResponseWriter, r *http.Request) {
Noise: noise,
Format: format,
}
d, err := submitdonk(w, r)
donks, err := submitdonk(w, r)
if err != nil && err != http.ErrMissingFile {
return
}
if d != nil {
ch.Donks = append(ch.Donks, d)
if len(donks) > 0 {
ch.Donks = append(ch.Donks, donks...)
}
translatechonk(&ch)
@ -2485,15 +2579,16 @@ func apihandler(w http.ResponseWriter, r *http.Request) {
}
fmt.Fprintf(w, "%s", h.XID)
case "donk":
d, err := submitdonk(w, r)
donks, err := submitdonk(w, r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if d == nil {
if len(donks) == 0 {
http.Error(w, "missing donk", http.StatusBadRequest)
return
}
d := donks[0]
donkxid := fmt.Sprintf("%s:%d", d.XID, d.FileID)
w.Write([]byte(donkxid))
case "zonkit":
@ -2643,6 +2738,22 @@ func emuinit() {
})
}
var savedassetparams = make(map[string]string)
func getassetparam(file string) string {
if p, ok := savedassetparams[file]; ok {
return p
}
data, err := os.ReadFile(file)
if err != nil {
return ""
}
hasher := sha512.New()
hasher.Write(data)
return fmt.Sprintf("?v=%.8x", hasher.Sum(nil))
}
func serve() {
db := opendatabase()
login.Init(login.InitArgs{Db: db, Logger: ilog, Insecure: develMode, SameSiteStrict: !develMode})
@ -2685,6 +2796,7 @@ func serve() {
viewDir + "/views/style.css",
dataDir + "/views/local.css",
viewDir + "/views/honkpage.js",
viewDir + "/views/misc.js",
dataDir + "/views/local.js",
}
for _, s := range assets {