Compare commits
51 Commits
887bf8e8a6
...
58ecc76b90
Author | SHA1 | Date |
---|---|---|
Ted Unangst | 58ecc76b90 | |
Ted Unangst | 386dc6f798 | |
Ted Unangst | dbbd4468fa | |
Ted Unangst | c750122b7f | |
Ted Unangst | 82c7e22193 | |
Ted Unangst | 850ec873a8 | |
Ted Unangst | 90bef0b0b9 | |
Ted Unangst | 81bb8099ae | |
Ted Unangst | 1a00ca5b43 | |
Ted Unangst | 281570fe6b | |
Ted Unangst | ae3843690f | |
Ted Unangst | 0a66a0e13a | |
Ted Unangst | b60e2e6156 | |
Ted Unangst | 99aed03a36 | |
Ted Unangst | 8380a32139 | |
Ted Unangst | 7dc99cec9b | |
Ted Unangst | 1a1c56200c | |
Ted Unangst | 76da4515bc | |
Ted Unangst | a6bc763507 | |
Ted Unangst | 15572f2e21 | |
Ted Unangst | a4817e149b | |
Ted Unangst | aad5243bb1 | |
Ted Unangst | b539f292a6 | |
Ted Unangst | cc3976daa6 | |
Ted Unangst | d69156f96e | |
Ted Unangst | b360a01719 | |
Ted Unangst | 3bbd1ad2b3 | |
Ted Unangst | a4be8b9e02 | |
Ted Unangst | 0dd17d0038 | |
Ted Unangst | 1b36e72cc1 | |
Ted Unangst | 314949d045 | |
Ted Unangst | db63bd3fc3 | |
Ted Unangst | 86738e30b8 | |
Ted Unangst | 0ba146c00c | |
Ted Unangst | 2c5a051376 | |
Ted Unangst | 17f2f729ee | |
Ted Unangst | 6f5bf49fca | |
Ted Unangst | bddd8554a2 | |
Ted Unangst | dbff9a6151 | |
Ted Unangst | 4f0d15ebb5 | |
Ted Unangst | 634d74d139 | |
Ted Unangst | 93a79455d2 | |
Ted Unangst | f7cd268fe2 | |
Ted Unangst | 57e34c344d | |
Ted Unangst | f4c4c8a917 | |
Ted Unangst | 773eb649a1 | |
Ted Unangst | 9658fb8605 | |
Ted Unangst | 20699112a0 | |
Ted Unangst | 842816f1bf | |
Ted Unangst | 1910279c6b | |
Ted Unangst | 0e4a87c523 |
|
@ -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
|
||||
|
|
2
.hgtags
2
.hgtags
|
@ -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
|
||||
|
|
5
Makefile
5
Makefile
|
@ -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
2
README
|
@ -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.
|
||||
|
||||
|
|
36
activity.go
36
activity.go
|
@ -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
|
||||
|
|
25
backend.go
25
backend.go
|
@ -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)
|
||||
|
|
10
backupdb.go
10
backupdb.go
|
@ -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)")
|
||||
|
|
32
database.go
32
database.go
|
@ -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)
|
||||
|
|
|
@ -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:]
|
||||
|
|
|
@ -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.
|
||||
|
|
19
docs/honk.1
19
docs/honk.1
|
@ -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
|
||||
|
|
14
docs/honk.8
14
docs/honk.8
|
@ -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
5
fun.go
|
@ -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
13
go.mod
|
@ -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
43
go.sum
|
@ -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
179
import.go
|
@ -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
73
main.go
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build openbsd
|
||||
// +build openbsd
|
||||
|
||||
//
|
||||
// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
|
||||
|
|
|
@ -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
18
util.go
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 }}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
162
web.go
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue