Add AI-generated-summary plugin (aitldr), many new plugin hooks and update dependencies

This commit is contained in:
Jan-Lukas Else 2023-03-24 21:25:20 +01:00
parent d5db07fb73
commit 609780db79
14 changed files with 492 additions and 103 deletions

View File

@ -70,7 +70,7 @@ func (a *goBlog) openDatabase(file string, logging bool) (*database, error) {
ConnectHook: func(c *sqlite.SQLiteConn) error { ConnectHook: func(c *sqlite.SQLiteConn) error {
// Register functions // Register functions
for n, f := range map[string]any{ for n, f := range map[string]any{
"mdtext": a.renderText, "mdtext": a.renderTextSafe,
"tolocal": toLocalSafe, "tolocal": toLocalSafe,
"toutc": toUTCSafe, "toutc": toUTCSafe,
"wordcount": wordCount, "wordcount": wordCount,

View File

@ -27,17 +27,9 @@ You need to specify the path to the plugin (remember to mount the path to your G
## Types of plugins ## Types of plugins
- `SetApp` (Access more GoBlog functionalities like the database) - see [https://pkg.go.dev/go.goblog.app/app/pkgs/plugintypes#SetApp] There are different plugin types for different functionalities a plugin wants to support. A plugin can implement multiple plugin types. You can find more information about the plugin types [in the Go documentation](https://pkg.go.dev/go.goblog.app/app/pkgs/plugintypes).
- `SetConfig` (Access the configuration provided for the plugin) - see [https://pkg.go.dev/go.goblog.app/app/pkgs/plugintypes#SetConfig]
- `Exec` (Command that is executed in a Go routine when starting GoBlog) - see [https://pkg.go.dev/go.goblog.app/app/pkgs/plugintypes#Exec] You can take a look at the demo plugin, which implements many of the plugin types.
- `Middleware` (HTTP middleware to intercept or modify HTTP requests) - see [https://pkg.go.dev/go.goblog.app/app/pkgs/]plugintypes#Middleware
- `UI` (Modify rendered HTML) - see [https://pkg.go.dev/go.goblog.app/app/pkgs/plugintypes#UI]
- `UI2` (Modify rendered HTML using a goquery document which improves performance and avoids multiple HTML parsing and rendering when using multiple plugins) - see [https://pkg.go.dev/go.goblog.app/app/pkgs/plugintypes#UI2]
- `UISummary` (like UI2 for only the post summary on indexes) - see [https://pkg.go.dev/go.goblog.app/app/pkgs/plugintypes#UISummary]
- `UIFooter` (like UI2 for only the post summary on indexes) - see [https://pkg.go.dev/go.goblog.app/app/pkgs/plugintypes#UIFooter]
More types will be added later. Any plugin can implement multiple types, see the demo plugin as example.
## Plugin implementation ## Plugin implementation
@ -130,4 +122,23 @@ config:
link: https://example.org/ # Link to the webring link: https://example.org/ # Link to the webring
prev: https://example.com/ # Link to previous webring site prev: https://example.com/ # Link to previous webring site
next: https://example.net/ # Link to next webring site next: https://example.net/ # Link to next webring site
```
### AI generated summary (Path `embedded:aitldr`, Import `aitldr`)
A plugin that uses the ChatGPT API to generated a short one-sentence summary for the blog post (after creating or updating it). To prevent it from generating a summary for a post, add the following post parameter:
```yaml
noaitldr: "true"
```
#### Config
```yaml
config:
# Required
apikey: YOUR_OPEN_AI_API_KEY
# Optional:
default: # Name of the blog
title: "Custom title for the summary box:"
``` ```

16
go.mod
View File

@ -8,7 +8,7 @@ require (
git.jlel.se/jlelse/goldmark-mark v0.0.0-20210522162520-9788c89266a4 git.jlel.se/jlelse/goldmark-mark v0.0.0-20210522162520-9788c89266a4
git.jlel.se/jlelse/template-strings v0.0.0-20220211095702-c012e3b5045b git.jlel.se/jlelse/template-strings v0.0.0-20220211095702-c012e3b5045b
github.com/PuerkitoBio/goquery v1.8.1 github.com/PuerkitoBio/goquery v1.8.1
github.com/alecthomas/chroma/v2 v2.5.0 github.com/alecthomas/chroma/v2 v2.7.0
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b
github.com/carlmjohnson/requests v0.23.2 github.com/carlmjohnson/requests v0.23.2
@ -20,8 +20,8 @@ require (
github.com/dmulholl/mp3lib v1.0.0 github.com/dmulholl/mp3lib v1.0.0
github.com/elnormous/contenttype v1.0.4 github.com/elnormous/contenttype v1.0.4
github.com/emersion/go-smtp v0.16.0 github.com/emersion/go-smtp v0.16.0
github.com/go-ap/activitypub v0.0.0-20230317030458-892480c77bb6 github.com/go-ap/activitypub v0.0.0-20230323123728-77b329013634
github.com/go-ap/client v0.0.0-20230317030549-9bf6268ae536 github.com/go-ap/client v0.0.0-20230323123805-a1114dc5ba4f
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
github.com/go-chi/chi/v5 v5.0.8 github.com/go-chi/chi/v5 v5.0.8
github.com/go-fed/httpsig v1.1.0 github.com/go-fed/httpsig v1.1.0
@ -46,7 +46,7 @@ require (
github.com/paulmach/go.geojson v1.4.0 github.com/paulmach/go.geojson v1.4.0
github.com/posener/wstest v1.2.0 github.com/posener/wstest v1.2.0
github.com/pquerna/otp v1.4.0 github.com/pquerna/otp v1.4.0
github.com/samber/lo v1.37.0 github.com/samber/lo v1.38.1
github.com/schollz/sqlite3dump v1.3.1 github.com/schollz/sqlite3dump v1.3.1
github.com/snabb/sitemap v1.0.4 github.com/snabb/sitemap v1.0.4
github.com/sourcegraph/conc v0.3.0 github.com/sourcegraph/conc v0.3.0
@ -57,7 +57,7 @@ require (
// master // master
github.com/tkrajina/gpxgo v1.2.2-0.20220217201249-321f19554eec github.com/tkrajina/gpxgo v1.2.2-0.20220217201249-321f19554eec
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
github.com/traefik/yaegi v0.15.0 github.com/traefik/yaegi v0.15.1
github.com/vcraescu/go-paginator/v2 v2.0.0 github.com/vcraescu/go-paginator/v2 v2.0.0
github.com/xhit/go-simple-mail/v2 v2.13.0 github.com/xhit/go-simple-mail/v2 v2.13.0
github.com/yuin/goldmark v1.5.4 github.com/yuin/goldmark v1.5.4
@ -88,7 +88,7 @@ require (
github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gin-gonic/gin v1.7.7 // indirect github.com/gin-gonic/gin v1.7.7 // indirect
github.com/go-ap/errors v0.0.0-20221205040414-01c1adfc98ea // indirect github.com/go-ap/errors v0.0.0-20221205040414-01c1adfc98ea // indirect
github.com/golang/glog v1.1.0 // indirect github.com/golang/glog v1.1.1 // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/gorilla/css v1.0.0 // indirect github.com/gorilla/css v1.0.0 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect github.com/gorilla/securecookie v1.1.1 // indirect
@ -100,7 +100,7 @@ require (
github.com/lestrrat-go/strftime v1.0.6 // indirect github.com/lestrrat-go/strftime v1.0.6 // indirect
github.com/magiconair/properties v1.8.7 // indirect github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mmcdole/goxpp v1.1.0 // indirect github.com/mmcdole/goxpp v1.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
@ -123,7 +123,7 @@ require (
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect
github.com/valyala/fastjson v1.6.4 // indirect github.com/valyala/fastjson v1.6.4 // indirect
go.uber.org/multierr v1.10.0 // indirect go.uber.org/multierr v1.10.0 // indirect
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 // indirect golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
golang.org/x/image v0.6.0 // indirect golang.org/x/image v0.6.0 // indirect
golang.org/x/oauth2 v0.6.0 // indirect golang.org/x/oauth2 v0.6.0 // indirect
golang.org/x/sys v0.6.0 // indirect golang.org/x/sys v0.6.0 // indirect

32
go.sum
View File

@ -53,8 +53,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink= github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
github.com/alecthomas/chroma/v2 v2.5.0 h1:CQCdj1BiBV17sD4Bd32b/Bzuiq/EqoNTrnIhyQAZ+Rk= github.com/alecthomas/chroma/v2 v2.7.0 h1:hm1rY6c/Ob4eGclpQ7X/A3yhqBOZNUTk9q+yhyLIViI=
github.com/alecthomas/chroma/v2 v2.5.0/go.mod h1:yrkMI9807G1ROx13fhe1v6PN2DDeaR73L3d+1nmYQtw= github.com/alecthomas/chroma/v2 v2.7.0/go.mod h1:yrkMI9807G1ROx13fhe1v6PN2DDeaR73L3d+1nmYQtw=
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
@ -128,10 +128,10 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
github.com/go-ap/activitypub v0.0.0-20230317030458-892480c77bb6 h1:I6l80Wy1UK0KZvK0xLho5FkiHp84f77RjgYOPThQ2I0= github.com/go-ap/activitypub v0.0.0-20230323123728-77b329013634 h1:zD/tSS22PgVrJTJatsefCvug/RjabVy6JmshKYzOQok=
github.com/go-ap/activitypub v0.0.0-20230317030458-892480c77bb6/go.mod h1:1oVD0h0aPT3OEE1ZoSUoym/UGKzxe+e0y8K2AkQ1Hqs= github.com/go-ap/activitypub v0.0.0-20230323123728-77b329013634/go.mod h1:qw0WNf+PTG69Xu6mVqUluDuKl1VwVYdgntOZQFBZQ48=
github.com/go-ap/client v0.0.0-20230317030549-9bf6268ae536 h1:+7v15882cEa9nRbjgk3D17qRcJgevDYvtcMUs5uvOkU= github.com/go-ap/client v0.0.0-20230323123805-a1114dc5ba4f h1:ZOQfbSNAsQOLa/c3/mRCOMSSXjOnAyCMdiJ9myJiYBk=
github.com/go-ap/client v0.0.0-20230317030549-9bf6268ae536/go.mod h1:qgiGGIKang+sVcINaB3nN5uw4wZtaHHqp/qqtZ2HI2Y= github.com/go-ap/client v0.0.0-20230323123805-a1114dc5ba4f/go.mod h1:ChxiPiPaRRYpsEFAX3KGAeE9P9upancoJTRSaaudpJE=
github.com/go-ap/errors v0.0.0-20221205040414-01c1adfc98ea h1:ywGtLGVjJjMrq4mu35Qmu+NtlhlTk/gTayE6Bb4tQZk= github.com/go-ap/errors v0.0.0-20221205040414-01c1adfc98ea h1:ywGtLGVjJjMrq4mu35Qmu+NtlhlTk/gTayE6Bb4tQZk=
github.com/go-ap/errors v0.0.0-20221205040414-01c1adfc98ea/go.mod h1:SaTNjEEkp0q+w3pUS1ccyEL/lUrHteORlDq/e21mCc8= github.com/go-ap/errors v0.0.0-20221205040414-01c1adfc98ea/go.mod h1:SaTNjEEkp0q+w3pUS1ccyEL/lUrHteORlDq/e21mCc8=
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 h1:GMKIYXyXPGIp+hYiWOhfqK4A023HdgisDT4YGgf99mw= github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 h1:GMKIYXyXPGIp+hYiWOhfqK4A023HdgisDT4YGgf99mw=
@ -163,8 +163,8 @@ github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= github.com/golang/glog v1.1.1 h1:jxpi2eWoU84wbX9iIEyAeeoac3FLuifZpY9tcNUD9kw=
github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/glog v1.1.1/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -310,8 +310,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
@ -354,8 +354,8 @@ github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w= github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w=
github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw= github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA= github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/schollz/sqlite3dump v1.3.1 h1:QXizJ7XEJ7hggjqjZ3YRtF3+javm8zKtzNByYtEkPRA= github.com/schollz/sqlite3dump v1.3.1 h1:QXizJ7XEJ7hggjqjZ3YRtF3+javm8zKtzNByYtEkPRA=
github.com/schollz/sqlite3dump v1.3.1/go.mod h1:mzSTjZpJH4zAb1FN3iNlhWPbbdyeBpOaTW0hukyMHyI= github.com/schollz/sqlite3dump v1.3.1/go.mod h1:mzSTjZpJH4zAb1FN3iNlhWPbbdyeBpOaTW0hukyMHyI=
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg= github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
@ -412,8 +412,8 @@ github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJ
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE= github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE=
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 h1:PM5hJF7HVfNWmCjMdEfbuOBNXSVF2cMFGgQTPdKCbwM= github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 h1:PM5hJF7HVfNWmCjMdEfbuOBNXSVF2cMFGgQTPdKCbwM=
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns= github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns=
github.com/traefik/yaegi v0.15.0 h1:ScDDfQXTT75rKvcsMcP84rOxHsZ8b6NiQJyGocGDB7g= github.com/traefik/yaegi v0.15.1 h1:YA5SbaL6HZA0Exh9T/oArRHqGN2HQ+zgmCY7dkoTXu4=
github.com/traefik/yaegi v0.15.0/go.mod h1:AVRxhaI2G+nUsaM1zyktzwXn69G3t/AuTDrCiTds9p0= github.com/traefik/yaegi v0.15.1/go.mod h1:AVRxhaI2G+nUsaM1zyktzwXn69G3t/AuTDrCiTds9p0=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
@ -465,8 +465,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo= golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=

View File

@ -7,6 +7,7 @@ import (
"time" "time"
"go.goblog.app/app/pkgs/bufferpool" "go.goblog.app/app/pkgs/bufferpool"
"go.goblog.app/app/pkgs/plugintypes"
) )
func (a *goBlog) preStartHooks() { func (a *goBlog) preStartHooks() {
@ -35,6 +36,9 @@ func (a *goBlog) postPostHooks(p *post) {
for _, f := range a.pPostHooks { for _, f := range a.pPostHooks {
go f(p) go f(p)
} }
for _, plugin := range a.getPlugins(pluginPostCreatedHookType) {
go plugin.(plugintypes.PostCreatedHook).PostCreated(p)
}
} }
func (a *goBlog) postUpdateHooks(p *post) { func (a *goBlog) postUpdateHooks(p *post) {
@ -52,6 +56,9 @@ func (a *goBlog) postUpdateHooks(p *post) {
for _, f := range a.pUpdateHooks { for _, f := range a.pUpdateHooks {
go f(p) go f(p)
} }
for _, plugin := range a.getPlugins(pluginPostUpdatedHookType) {
go plugin.(plugintypes.PostUpdatedHook).PostUpdated(p)
}
} }
func (a *goBlog) postDeleteHooks(p *post) { func (a *goBlog) postDeleteHooks(p *post) {
@ -68,6 +75,9 @@ func (a *goBlog) postDeleteHooks(p *post) {
for _, f := range a.pDeleteHooks { for _, f := range a.pDeleteHooks {
go f(p) go f(p)
} }
for _, plugin := range a.getPlugins(pluginPostDeletedHookType) {
go plugin.(plugintypes.PostDeletedHook).PostDeleted(p)
}
} }
func (a *goBlog) postUndeleteHooks(p *post) { func (a *goBlog) postUndeleteHooks(p *post) {

View File

@ -56,10 +56,7 @@ func (a *goBlog) initMarkdown() {
goldmark.WithParser( goldmark.WithParser(
// Override, no need for special Markdown parsers // Override, no need for special Markdown parsers
parser.NewParser( parser.NewParser(
parser.WithBlockParsers( parser.WithBlockParsers(util.Prioritized(parser.NewParagraphParser(), 1000)),
util.Prioritized(parser.NewParagraphParser(), 1000)),
parser.WithInlineParsers(),
parser.WithParagraphTransformers(),
), ),
), ),
goldmark.WithExtensions( goldmark.WithExtensions(
@ -78,9 +75,9 @@ func (a *goBlog) renderMarkdownToWriter(w io.Writer, source string, absoluteLink
return err return err
} }
func (a *goBlog) renderText(s string) string { func (a *goBlog) renderText(s string) (string, error) {
if s == "" { if s == "" {
return "" return "", nil
} }
pr, pw := io.Pipe() pr, pw := io.Pipe()
go func() { go func() {
@ -89,9 +86,14 @@ func (a *goBlog) renderText(s string) string {
text, err := htmlTextFromReader(pr) text, err := htmlTextFromReader(pr)
_ = pr.CloseWithError(err) _ = pr.CloseWithError(err)
if err != nil { if err != nil {
return "" return "", nil
} }
return text return text, nil
}
func (a *goBlog) renderTextSafe(s string) string {
r, _ := a.renderText(s)
return r
} }
func (a *goBlog) renderMdTitle(s string) string { func (a *goBlog) renderMdTitle(s string) string {

View File

@ -79,8 +79,9 @@ func Test_markdown(t *testing.T) {
// Text // Text
renderedText := app.renderText("**This** *is* [text](/)") renderedText, err := app.renderText("**This** *is* [text](/)")
assert.Equal(t, "This is text", renderedText) assert.Equal(t, "This is text", renderedText)
assert.NoError(t, err)
// Title // Title

View File

@ -13,6 +13,8 @@ type App interface {
GetDatabase() Database GetDatabase() Database
// Get a post from the database or an error when there is no post for the given path // Get a post from the database or an error when there is no post for the given path
GetPost(path string) (Post, error) GetPost(path string) (Post, error)
// Get a blog and a bool whether it exists
GetBlog(name string) (Blog, bool)
// Purge the rendering cache // Purge the rendering cache
PurgeCache() PurgeCache()
// Get the HTTP client used by GoBlog // Get the HTTP client used by GoBlog
@ -21,6 +23,10 @@ type App interface {
CompileAsset(filename string, reader io.Reader) error CompileAsset(filename string, reader io.Reader) error
// Get the asset path with the filename used when compiling the assert // Get the asset path with the filename used when compiling the assert
AssetPath(filename string) string AssetPath(filename string) string
// Set parameter values for a post path
SetPostParameter(path string, parameter string, values []string) error
// Render markdown as text (without HTML)
RenderMarkdownAsText(markdown string) (text string, err error)
} }
// Database is used to provide access to GoBlog's database. // Database is used to provide access to GoBlog's database.
@ -33,14 +39,18 @@ type Database interface {
QueryRowContext(context.Context, string, ...any) (*sql.Row, error) QueryRowContext(context.Context, string, ...any) (*sql.Row, error)
} }
// Post // Post contains methods to access the post's data.
type Post interface { type Post interface {
// Get the post path // Get the post path
GetPath() string GetPath() string
// Get the blog name
GetBlog() string
// Get a string array map with all the post's parameters // Get a string array map with all the post's parameters
GetParameters() map[string][]string GetParameters() map[string][]string
// Get a single parameter array (a parameter can have multiple values) // Get a single parameter array (a parameter can have multiple values)
GetParameter(parameter string) []string GetParameter(parameter string) []string
// Get the first value of a post parameter
GetFirstParameterValue(parameter string) string
// Get the post section name // Get the post section name
GetSection() string GetSection() string
// Get the published date string // Get the published date string
@ -49,9 +59,17 @@ type Post interface {
GetUpdated() string GetUpdated() string
// Get the post content (markdown) // Get the post content (markdown)
GetContent() string GetContent() string
// Get the post title
GetTitle() string
} }
// RenderContext // Blog contains methods to access the blog's configuration.
type Blog interface {
// Get the language
GetLanguage() string
}
// RenderContext gives some context of the current rendering.
type RenderContext interface { type RenderContext interface {
// Get the path of the request // Get the path of the request
GetPath() string GetPath() string

View File

@ -52,9 +52,35 @@ type UISummary interface {
RenderSummaryForPost(renderContext RenderContext, post Post, doc *goquery.Document) RenderSummaryForPost(renderContext RenderContext, post Post, doc *goquery.Document)
} }
// UIPost plugins get called when rendering the h-entry for a post. But only on the HTML frontend, not ActivityPub or feeds.
type UIPost interface {
// The renderContext provides information such as the path of the request or the blog name.
// The post contains information about the post for which to render the summary.
// The document can be used to add or modify the default HTML. But it only contains the HTML for the post, not for the whole page.
RenderPost(renderContext RenderContext, post Post, doc *goquery.Document)
}
// UIFooter plugins get called when rendering the footer on each HTML page. // UIFooter plugins get called when rendering the footer on each HTML page.
type UIFooter interface { type UIFooter interface {
// The renderContext provides information such as the path of the request or the blog name. // The renderContext provides information such as the path of the request or the blog name.
// The document can be used to add or modify the default HTML. // The document can be used to add or modify the default HTML.
RenderFooter(renderContext RenderContext, doc *goquery.Document) RenderFooter(renderContext RenderContext, doc *goquery.Document)
} }
// PostCreatedHook plugins get called after a post is created.
type PostCreatedHook interface {
// Handle the post.
PostCreated(post Post)
}
// PostUpdatedHook plugins get called after a post is updated.
type PostUpdatedHook interface {
// Handle the post.
PostUpdated(post Post)
}
// PostUpdatedHook plugins get called after a post is deleted.
type PostDeletedHook interface {
// Handle the post.
PostDeleted(post Post)
}

View File

@ -37,44 +37,57 @@ import (
func init() { func init() {
Symbols["go.goblog.app/app/pkgs/plugintypes/plugintypes"] = map[string]reflect.Value{ Symbols["go.goblog.app/app/pkgs/plugintypes/plugintypes"] = map[string]reflect.Value{
// type definitions // type definitions
"App": reflect.ValueOf((*plugintypes.App)(nil)), "App": reflect.ValueOf((*plugintypes.App)(nil)),
"Database": reflect.ValueOf((*plugintypes.Database)(nil)), "Blog": reflect.ValueOf((*plugintypes.Blog)(nil)),
"Exec": reflect.ValueOf((*plugintypes.Exec)(nil)), "Database": reflect.ValueOf((*plugintypes.Database)(nil)),
"Middleware": reflect.ValueOf((*plugintypes.Middleware)(nil)), "Exec": reflect.ValueOf((*plugintypes.Exec)(nil)),
"Post": reflect.ValueOf((*plugintypes.Post)(nil)), "Middleware": reflect.ValueOf((*plugintypes.Middleware)(nil)),
"RenderContext": reflect.ValueOf((*plugintypes.RenderContext)(nil)), "Post": reflect.ValueOf((*plugintypes.Post)(nil)),
"SetApp": reflect.ValueOf((*plugintypes.SetApp)(nil)), "PostCreatedHook": reflect.ValueOf((*plugintypes.PostCreatedHook)(nil)),
"SetConfig": reflect.ValueOf((*plugintypes.SetConfig)(nil)), "PostDeletedHook": reflect.ValueOf((*plugintypes.PostDeletedHook)(nil)),
"UI": reflect.ValueOf((*plugintypes.UI)(nil)), "PostUpdatedHook": reflect.ValueOf((*plugintypes.PostUpdatedHook)(nil)),
"UI2": reflect.ValueOf((*plugintypes.UI2)(nil)), "RenderContext": reflect.ValueOf((*plugintypes.RenderContext)(nil)),
"UIFooter": reflect.ValueOf((*plugintypes.UIFooter)(nil)), "SetApp": reflect.ValueOf((*plugintypes.SetApp)(nil)),
"UISummary": reflect.ValueOf((*plugintypes.UISummary)(nil)), "SetConfig": reflect.ValueOf((*plugintypes.SetConfig)(nil)),
"UI": reflect.ValueOf((*plugintypes.UI)(nil)),
"UI2": reflect.ValueOf((*plugintypes.UI2)(nil)),
"UIFooter": reflect.ValueOf((*plugintypes.UIFooter)(nil)),
"UIPost": reflect.ValueOf((*plugintypes.UIPost)(nil)),
"UISummary": reflect.ValueOf((*plugintypes.UISummary)(nil)),
// interface wrapper definitions // interface wrapper definitions
"_App": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_App)(nil)), "_App": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_App)(nil)),
"_Database": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_Database)(nil)), "_Blog": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_Blog)(nil)),
"_Exec": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_Exec)(nil)), "_Database": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_Database)(nil)),
"_Middleware": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_Middleware)(nil)), "_Exec": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_Exec)(nil)),
"_Post": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_Post)(nil)), "_Middleware": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_Middleware)(nil)),
"_RenderContext": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_RenderContext)(nil)), "_Post": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_Post)(nil)),
"_SetApp": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_SetApp)(nil)), "_PostCreatedHook": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_PostCreatedHook)(nil)),
"_SetConfig": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_SetConfig)(nil)), "_PostDeletedHook": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_PostDeletedHook)(nil)),
"_UI": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_UI)(nil)), "_PostUpdatedHook": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_PostUpdatedHook)(nil)),
"_UI2": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_UI2)(nil)), "_RenderContext": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_RenderContext)(nil)),
"_UIFooter": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_UIFooter)(nil)), "_SetApp": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_SetApp)(nil)),
"_UISummary": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_UISummary)(nil)), "_SetConfig": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_SetConfig)(nil)),
"_UI": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_UI)(nil)),
"_UI2": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_UI2)(nil)),
"_UIFooter": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_UIFooter)(nil)),
"_UIPost": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_UIPost)(nil)),
"_UISummary": reflect.ValueOf((*_go_goblog_app_app_pkgs_plugintypes_UISummary)(nil)),
} }
} }
// _go_goblog_app_app_pkgs_plugintypes_App is an interface wrapper for App type // _go_goblog_app_app_pkgs_plugintypes_App is an interface wrapper for App type
type _go_goblog_app_app_pkgs_plugintypes_App struct { type _go_goblog_app_app_pkgs_plugintypes_App struct {
IValue interface{} IValue interface{}
WAssetPath func(filename string) string WAssetPath func(filename string) string
WCompileAsset func(filename string, reader io.Reader) error WCompileAsset func(filename string, reader io.Reader) error
WGetDatabase func() plugintypes.Database WGetBlog func(name string) (plugintypes.Blog, bool)
WGetHTTPClient func() *http.Client WGetDatabase func() plugintypes.Database
WGetPost func(path string) (plugintypes.Post, error) WGetHTTPClient func() *http.Client
WPurgeCache func() WGetPost func(path string) (plugintypes.Post, error)
WPurgeCache func()
WRenderMarkdownAsText func(markdown string) (text string, err error)
WSetPostParameter func(path string, parameter string, values []string) error
} }
func (W _go_goblog_app_app_pkgs_plugintypes_App) AssetPath(filename string) string { func (W _go_goblog_app_app_pkgs_plugintypes_App) AssetPath(filename string) string {
@ -83,6 +96,9 @@ func (W _go_goblog_app_app_pkgs_plugintypes_App) AssetPath(filename string) stri
func (W _go_goblog_app_app_pkgs_plugintypes_App) CompileAsset(filename string, reader io.Reader) error { func (W _go_goblog_app_app_pkgs_plugintypes_App) CompileAsset(filename string, reader io.Reader) error {
return W.WCompileAsset(filename, reader) return W.WCompileAsset(filename, reader)
} }
func (W _go_goblog_app_app_pkgs_plugintypes_App) GetBlog(name string) (plugintypes.Blog, bool) {
return W.WGetBlog(name)
}
func (W _go_goblog_app_app_pkgs_plugintypes_App) GetDatabase() plugintypes.Database { func (W _go_goblog_app_app_pkgs_plugintypes_App) GetDatabase() plugintypes.Database {
return W.WGetDatabase() return W.WGetDatabase()
} }
@ -95,6 +111,22 @@ func (W _go_goblog_app_app_pkgs_plugintypes_App) GetPost(path string) (plugintyp
func (W _go_goblog_app_app_pkgs_plugintypes_App) PurgeCache() { func (W _go_goblog_app_app_pkgs_plugintypes_App) PurgeCache() {
W.WPurgeCache() W.WPurgeCache()
} }
func (W _go_goblog_app_app_pkgs_plugintypes_App) RenderMarkdownAsText(markdown string) (text string, err error) {
return W.WRenderMarkdownAsText(markdown)
}
func (W _go_goblog_app_app_pkgs_plugintypes_App) SetPostParameter(path string, parameter string, values []string) error {
return W.WSetPostParameter(path, parameter, values)
}
// _go_goblog_app_app_pkgs_plugintypes_Blog is an interface wrapper for Blog type
type _go_goblog_app_app_pkgs_plugintypes_Blog struct {
IValue interface{}
WGetLanguage func() string
}
func (W _go_goblog_app_app_pkgs_plugintypes_Blog) GetLanguage() string {
return W.WGetLanguage()
}
// _go_goblog_app_app_pkgs_plugintypes_Database is an interface wrapper for Database type // _go_goblog_app_app_pkgs_plugintypes_Database is an interface wrapper for Database type
type _go_goblog_app_app_pkgs_plugintypes_Database struct { type _go_goblog_app_app_pkgs_plugintypes_Database struct {
@ -152,19 +184,28 @@ func (W _go_goblog_app_app_pkgs_plugintypes_Middleware) Prio() int {
// _go_goblog_app_app_pkgs_plugintypes_Post is an interface wrapper for Post type // _go_goblog_app_app_pkgs_plugintypes_Post is an interface wrapper for Post type
type _go_goblog_app_app_pkgs_plugintypes_Post struct { type _go_goblog_app_app_pkgs_plugintypes_Post struct {
IValue interface{} IValue interface{}
WGetContent func() string WGetBlog func() string
WGetParameter func(parameter string) []string WGetContent func() string
WGetParameters func() map[string][]string WGetFirstParameterValue func(parameter string) string
WGetPath func() string WGetParameter func(parameter string) []string
WGetPublished func() string WGetParameters func() map[string][]string
WGetSection func() string WGetPath func() string
WGetUpdated func() string WGetPublished func() string
WGetSection func() string
WGetTitle func() string
WGetUpdated func() string
} }
func (W _go_goblog_app_app_pkgs_plugintypes_Post) GetBlog() string {
return W.WGetBlog()
}
func (W _go_goblog_app_app_pkgs_plugintypes_Post) GetContent() string { func (W _go_goblog_app_app_pkgs_plugintypes_Post) GetContent() string {
return W.WGetContent() return W.WGetContent()
} }
func (W _go_goblog_app_app_pkgs_plugintypes_Post) GetFirstParameterValue(parameter string) string {
return W.WGetFirstParameterValue(parameter)
}
func (W _go_goblog_app_app_pkgs_plugintypes_Post) GetParameter(parameter string) []string { func (W _go_goblog_app_app_pkgs_plugintypes_Post) GetParameter(parameter string) []string {
return W.WGetParameter(parameter) return W.WGetParameter(parameter)
} }
@ -180,10 +221,43 @@ func (W _go_goblog_app_app_pkgs_plugintypes_Post) GetPublished() string {
func (W _go_goblog_app_app_pkgs_plugintypes_Post) GetSection() string { func (W _go_goblog_app_app_pkgs_plugintypes_Post) GetSection() string {
return W.WGetSection() return W.WGetSection()
} }
func (W _go_goblog_app_app_pkgs_plugintypes_Post) GetTitle() string {
return W.WGetTitle()
}
func (W _go_goblog_app_app_pkgs_plugintypes_Post) GetUpdated() string { func (W _go_goblog_app_app_pkgs_plugintypes_Post) GetUpdated() string {
return W.WGetUpdated() return W.WGetUpdated()
} }
// _go_goblog_app_app_pkgs_plugintypes_PostCreatedHook is an interface wrapper for PostCreatedHook type
type _go_goblog_app_app_pkgs_plugintypes_PostCreatedHook struct {
IValue interface{}
WPostCreated func(post plugintypes.Post)
}
func (W _go_goblog_app_app_pkgs_plugintypes_PostCreatedHook) PostCreated(post plugintypes.Post) {
W.WPostCreated(post)
}
// _go_goblog_app_app_pkgs_plugintypes_PostDeletedHook is an interface wrapper for PostDeletedHook type
type _go_goblog_app_app_pkgs_plugintypes_PostDeletedHook struct {
IValue interface{}
WPostDeleted func(post plugintypes.Post)
}
func (W _go_goblog_app_app_pkgs_plugintypes_PostDeletedHook) PostDeleted(post plugintypes.Post) {
W.WPostDeleted(post)
}
// _go_goblog_app_app_pkgs_plugintypes_PostUpdatedHook is an interface wrapper for PostUpdatedHook type
type _go_goblog_app_app_pkgs_plugintypes_PostUpdatedHook struct {
IValue interface{}
WPostUpdated func(post plugintypes.Post)
}
func (W _go_goblog_app_app_pkgs_plugintypes_PostUpdatedHook) PostUpdated(post plugintypes.Post) {
W.WPostUpdated(post)
}
// _go_goblog_app_app_pkgs_plugintypes_RenderContext is an interface wrapper for RenderContext type // _go_goblog_app_app_pkgs_plugintypes_RenderContext is an interface wrapper for RenderContext type
type _go_goblog_app_app_pkgs_plugintypes_RenderContext struct { type _go_goblog_app_app_pkgs_plugintypes_RenderContext struct {
IValue interface{} IValue interface{}
@ -252,6 +326,16 @@ func (W _go_goblog_app_app_pkgs_plugintypes_UIFooter) RenderFooter(renderContext
W.WRenderFooter(renderContext, doc) W.WRenderFooter(renderContext, doc)
} }
// _go_goblog_app_app_pkgs_plugintypes_UIPost is an interface wrapper for UIPost type
type _go_goblog_app_app_pkgs_plugintypes_UIPost struct {
IValue interface{}
WRenderPost func(renderContext plugintypes.RenderContext, post plugintypes.Post, doc *goquery.Document)
}
func (W _go_goblog_app_app_pkgs_plugintypes_UIPost) RenderPost(renderContext plugintypes.RenderContext, post plugintypes.Post, doc *goquery.Document) {
W.WRenderPost(renderContext, post, doc)
}
// _go_goblog_app_app_pkgs_plugintypes_UISummary is an interface wrapper for UISummary type // _go_goblog_app_app_pkgs_plugintypes_UISummary is an interface wrapper for UISummary type
type _go_goblog_app_app_pkgs_plugintypes_UISummary struct { type _go_goblog_app_app_pkgs_plugintypes_UISummary struct {
IValue interface{} IValue interface{}

View File

@ -16,14 +16,18 @@ import (
var pluginsFS embed.FS var pluginsFS embed.FS
const ( const (
pluginSetAppType = "setapp" pluginSetAppType = "setapp"
pluginSetConfigType = "setconfig" pluginSetConfigType = "setconfig"
pluginUiType = "ui" pluginUiType = "ui"
pluginUi2Type = "ui2" pluginUi2Type = "ui2"
pluginExecType = "exec" pluginExecType = "exec"
pluginMiddlewareType = "middleware" pluginMiddlewareType = "middleware"
pluginUiSummaryType = "uisummary" pluginUiSummaryType = "uisummary"
pluginUiFooterType = "uifooter" pluginUiPostType = "uiPost"
pluginUiFooterType = "uifooter"
pluginPostCreatedHookType = "postcreatedhook"
pluginPostUpdatedHookType = "postupdatedhook"
pluginPostDeletedHookType = "postdeletedhook"
) )
func (a *goBlog) initPlugins() error { func (a *goBlog) initPlugins() error {
@ -33,14 +37,18 @@ func (a *goBlog) initPlugins() error {
} }
a.pluginHost = plugins.NewPluginHost( a.pluginHost = plugins.NewPluginHost(
map[string]reflect.Type{ map[string]reflect.Type{
pluginSetAppType: reflect.TypeOf((*plugintypes.SetApp)(nil)).Elem(), pluginSetAppType: reflect.TypeOf((*plugintypes.SetApp)(nil)).Elem(),
pluginSetConfigType: reflect.TypeOf((*plugintypes.SetConfig)(nil)).Elem(), pluginSetConfigType: reflect.TypeOf((*plugintypes.SetConfig)(nil)).Elem(),
pluginUiType: reflect.TypeOf((*plugintypes.UI)(nil)).Elem(), pluginUiType: reflect.TypeOf((*plugintypes.UI)(nil)).Elem(),
pluginUi2Type: reflect.TypeOf((*plugintypes.UI2)(nil)).Elem(), pluginUi2Type: reflect.TypeOf((*plugintypes.UI2)(nil)).Elem(),
pluginExecType: reflect.TypeOf((*plugintypes.Exec)(nil)).Elem(), pluginExecType: reflect.TypeOf((*plugintypes.Exec)(nil)).Elem(),
pluginMiddlewareType: reflect.TypeOf((*plugintypes.Middleware)(nil)).Elem(), pluginMiddlewareType: reflect.TypeOf((*plugintypes.Middleware)(nil)).Elem(),
pluginUiSummaryType: reflect.TypeOf((*plugintypes.UISummary)(nil)).Elem(), pluginUiSummaryType: reflect.TypeOf((*plugintypes.UISummary)(nil)).Elem(),
pluginUiFooterType: reflect.TypeOf((*plugintypes.UIFooter)(nil)).Elem(), pluginUiPostType: reflect.TypeOf((*plugintypes.UIPost)(nil)).Elem(),
pluginUiFooterType: reflect.TypeOf((*plugintypes.UIFooter)(nil)).Elem(),
pluginPostCreatedHookType: reflect.TypeOf((*plugintypes.PostCreatedHook)(nil)).Elem(),
pluginPostUpdatedHookType: reflect.TypeOf((*plugintypes.PostUpdatedHook)(nil)).Elem(),
pluginPostDeletedHookType: reflect.TypeOf((*plugintypes.PostDeletedHook)(nil)).Elem(),
}, },
yaegiwrappers.Symbols, yaegiwrappers.Symbols,
subFS, subFS,
@ -86,6 +94,11 @@ func (a *goBlog) GetPost(path string) (plugintypes.Post, error) {
return a.getPost(path) return a.getPost(path)
} }
func (a *goBlog) GetBlog(name string) (plugintypes.Blog, bool) {
blog, ok := a.cfg.Blogs[name]
return blog, ok
}
func (a *goBlog) PurgeCache() { func (a *goBlog) PurgeCache() {
a.cache.purge() a.cache.purge()
} }
@ -102,6 +115,14 @@ func (a *goBlog) AssetPath(filename string) string {
return a.assetFileName(filename) return a.assetFileName(filename)
} }
func (a *goBlog) SetPostParameter(path string, parameter string, values []string) error {
return a.db.replacePostParam(path, parameter, values)
}
func (a *goBlog) RenderMarkdownAsText(markdown string) (text string, err error) {
return a.renderText(markdown)
}
func (p *post) GetPath() string { func (p *post) GetPath() string {
return p.Path return p.Path
} }
@ -114,6 +135,10 @@ func (p *post) GetParameter(parameter string) []string {
return p.Parameters[parameter] return p.Parameters[parameter]
} }
func (p *post) GetFirstParameterValue(parameter string) string {
return p.firstParameter(parameter)
}
func (p *post) GetSection() string { func (p *post) GetSection() string {
return p.Section return p.Section
} }
@ -129,3 +154,15 @@ func (p *post) GetUpdated() string {
func (p *post) GetContent() string { func (p *post) GetContent() string {
return p.Content return p.Content
} }
func (p *post) GetTitle() string {
return p.Title()
}
func (p *post) GetBlog() string {
return p.Blog
}
func (b *configBlog) GetLanguage() string {
return b.Lang
}

View File

@ -0,0 +1,192 @@
package aitldr
import (
"context"
"log"
"net/http"
"strings"
"sync"
"github.com/PuerkitoBio/goquery"
"github.com/carlmjohnson/requests"
"go.goblog.app/app/pkgs/bufferpool"
"go.goblog.app/app/pkgs/htmlbuilder"
"go.goblog.app/app/pkgs/plugintypes"
)
type plugin struct {
app plugintypes.App
config map[string]any
initCSS sync.Once
}
func GetPlugin() (
plugintypes.SetConfig, plugintypes.SetApp,
plugintypes.PostCreatedHook, plugintypes.PostUpdatedHook,
plugintypes.UIPost, plugintypes.UI2,
) {
p := &plugin{}
return p, p, p, p, p, p
}
func (p *plugin) SetApp(app plugintypes.App) {
p.app = app
}
func (p *plugin) SetConfig(config map[string]any) {
p.config = config
}
func (p *plugin) PostCreated(post plugintypes.Post) {
p.summarize(post)
}
func (p *plugin) PostUpdated(post plugintypes.Post) {
p.summarize(post)
}
const postParam = "aitldr"
func (p *plugin) RenderPost(renderContext plugintypes.RenderContext, post plugintypes.Post, doc *goquery.Document) {
tldr := post.GetFirstParameterValue(postParam)
if tldr == "" {
return
}
title := "AI generated summary:"
if blogConfig, ok := p.config[renderContext.GetBlog()]; ok {
if blogConfigAsMap, ok := blogConfig.(map[string]any); ok {
if blogSpecificTitle, ok := blogConfigAsMap["title"]; ok {
if blogSpecificTitleAsString, ok := blogSpecificTitle.(string); ok {
title = blogSpecificTitleAsString
}
}
}
}
buf := bufferpool.Get()
defer bufferpool.Put(buf)
hw := htmlbuilder.NewHtmlBuilder(buf)
hw.WriteElementOpen("div", "class", "p aitldr")
hw.WriteElementOpen("b")
hw.WriteEscaped(title)
hw.WriteElementClose("b")
hw.WriteEscaped(" ")
hw.WriteElementOpen("i")
hw.WriteEscaped(tldr)
hw.WriteElementsClose("i", "div")
doc.Find(".e-content").BeforeHtml(buf.String())
}
const customCSS = ".aitldr { border: 1px dashed; padding: 1em; }"
func (p *plugin) RenderWithDocument(_ plugintypes.RenderContext, doc *goquery.Document) {
if p.app == nil {
return
}
// Init custom CSS for plugin
p.initCSS.Do(func() {
_ = p.app.CompileAsset("aitldr.css", strings.NewReader(customCSS))
})
// Check if page has AI TLDR, then add the custom CSS
doc.Find(".aitldr").First().Each(func(_ int, _ *goquery.Selection) {
buf := bufferpool.Get()
defer bufferpool.Put(buf)
hb := htmlbuilder.NewHtmlBuilder(buf)
hb.WriteElementOpen("link", "rel", "stylesheet", "href", p.app.AssetPath("aitldr.css"))
doc.Find("head").AppendHtml(buf.String())
})
}
type apiMessage struct {
Role string `json:"role"`
Content string `json:"content"`
}
type apiResponse struct {
Choices []struct {
Message apiMessage `json:"message"`
} `json:"choices"`
}
func (p *plugin) summarize(post plugintypes.Post) {
if post.GetFirstParameterValue("noaitldr") == "true" {
log.Println("aitldr: Skip summarizing", post.GetPath())
return
}
apikey := ""
if k, ok := p.config["apikey"]; ok {
if ks, ok := k.(string); ok {
apikey = ks
}
}
if apikey == "" {
log.Println("Config for aitldr plugin not correct! apikey missing!")
return
}
var response apiResponse
err := requests.URL("https://api.openai.com/v1/chat/completions").
Method(http.MethodPost).
Header("Authorization", "Bearer "+apikey).
BodyJSON(map[string]any{
"model": "gpt-3.5-turbo",
"temperature": 0,
"max_tokens": 200,
"messages": []apiMessage{
{
Role: "user",
Content: p.createPrompt(post),
},
},
}).
ToJSON(&response).
Fetch(context.Background())
if err != nil {
log.Println("aitldr plugin:", err.Error())
return
}
if len(response.Choices) < 1 {
return
}
summary := response.Choices[0].Message.Content
summary = strings.TrimSpace(summary)
err = p.app.SetPostParameter(post.GetPath(), postParam, []string{summary})
if err != nil {
log.Println("aitldr plugin:", err.Error())
return
}
p.app.PurgeCache()
}
func (p *plugin) createPrompt(post plugintypes.Post) string {
lang := "en"
if blog, ok := p.app.GetBlog(post.GetBlog()); ok {
if blogLang := blog.GetLanguage(); lang != "" {
lang = blogLang
}
}
prompt := "Summarize the content of following text in one sentence in the language \"" + lang + "\". Answer with just the summary.\n\n\n"
if title, err := p.app.RenderMarkdownAsText(post.GetTitle()); err == nil && title != "" {
prompt += title + "\n\n"
} else if err != nil {
log.Println("aitldr plugin: Rendering markdown as text failed:", err.Error())
}
if text, err := p.app.RenderMarkdownAsText(post.GetContent()); err == nil && text != "" {
prompt += text + "\n\n"
} else if err != nil {
log.Println("aitldr plugin: Rendering markdown as text failed:", err.Error())
}
return prompt
}

View File

@ -120,7 +120,7 @@ func (a *goBlog) postSummary(p *post) (summary string) {
splitted := strings.Split(p.Content, summaryDivider) splitted := strings.Split(p.Content, summaryDivider)
hasDivider := len(splitted) > 1 hasDivider := len(splitted) > 1
markdown := splitted[0] markdown := splitted[0]
summary = a.renderText(markdown) summary = a.renderTextSafe(markdown)
if !hasDivider { if !hasDivider {
summary = strings.Split(summary, "\n\n")[0] summary = strings.Split(summary, "\n\n")[0]
} }

10
ui.go
View File

@ -4,12 +4,14 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/PuerkitoBio/goquery"
"github.com/hacdias/indieauth/v3" "github.com/hacdias/indieauth/v3"
"github.com/kaorimatz/go-opml" "github.com/kaorimatz/go-opml"
"github.com/mergestat/timediff" "github.com/mergestat/timediff"
"github.com/samber/lo" "github.com/samber/lo"
"go.goblog.app/app/pkgs/contenttype" "go.goblog.app/app/pkgs/contenttype"
"go.goblog.app/app/pkgs/htmlbuilder" "go.goblog.app/app/pkgs/htmlbuilder"
"go.goblog.app/app/pkgs/plugintypes"
) )
func (a *goBlog) renderEditorPreview(hb *htmlbuilder.HtmlBuilder, bc *configBlog, p *post) { func (a *goBlog) renderEditorPreview(hb *htmlbuilder.HtmlBuilder, bc *configBlog, p *post) {
@ -847,7 +849,13 @@ func (a *goBlog) renderPost(hb *htmlbuilder.HtmlBuilder, rd *renderData) {
hb.WriteElementOpen("link", "rel", "shortlink", "href", su) hb.WriteElementOpen("link", "rel", "shortlink", "href", su)
} }
}, },
func(hb *htmlbuilder.HtmlBuilder) { func(origHb *htmlbuilder.HtmlBuilder) {
// Wrap plugins
hb, finish := a.wrapForPlugins(origHb, a.getPlugins(pluginUiPostType), func(plugin any, doc *goquery.Document) {
plugin.(plugintypes.UIPost).RenderPost(rd.prc, p, doc)
})
defer finish()
// Render...
hb.WriteElementOpen("main", "class", "h-entry") hb.WriteElementOpen("main", "class", "h-entry")
// URL (hidden just for microformats) // URL (hidden just for microformats)
hb.WriteElementOpen("data", "value", a.getFullAddress(p.Path), "class", "u-url hide") hb.WriteElementOpen("data", "value", a.getFullAddress(p.Path), "class", "u-url hide")