aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mastodon/filter.go50
-rw-r--r--renderer/model.go5
-rw-r--r--renderer/renderer.go1
-rw-r--r--service/service.go25
-rw-r--r--service/transport.go28
-rw-r--r--static/style.css8
-rw-r--r--templates/filters.tmpl40
-rw-r--r--templates/user.tmpl1
8 files changed, 158 insertions, 0 deletions
diff --git a/mastodon/filter.go b/mastodon/filter.go
new file mode 100644
index 0000000..55beac1
--- /dev/null
+++ b/mastodon/filter.go
@@ -0,0 +1,50 @@
+package mastodon
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "net/url"
+ "strconv"
+ "time"
+)
+
+type Filter struct {
+ ID string `json:"id"`
+ Phrase string `json:"phrase"`
+ Context []string `json:"context"`
+ WholeWord bool `json:"whole_word"`
+ ExpiresAt *time.Time `json:"expires_at"`
+ Irreversible bool `json:"irreversible"`
+}
+
+func (c *Client) GetFilters(ctx context.Context) ([]*Filter, error) {
+ var filters []*Filter
+ err := c.doAPI(ctx, http.MethodGet, "/api/v1/filters", nil, &filters, nil)
+ if err != nil {
+ return nil, err
+ }
+ return filters, nil
+}
+
+func (c *Client) AddFilter(ctx context.Context, phrase string, context []string, irreversible bool, wholeWord bool, expiresIn *time.Time) error {
+ params := url.Values{}
+ params.Set("phrase", phrase)
+ for i := range context {
+ params.Add("context[]", context[i])
+ }
+ params.Set("irreversible", strconv.FormatBool(irreversible))
+ params.Set("whole_word", strconv.FormatBool(wholeWord))
+ if expiresIn != nil {
+ params.Set("expires_in", expiresIn.Format(time.RFC3339))
+ }
+ err := c.doAPI(ctx, http.MethodPost, "/api/v1/filters", params, nil, nil)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (c *Client) RemoveFilter(ctx context.Context, id string) error {
+ return c.doAPI(ctx, http.MethodDelete, fmt.Sprintf("/api/v1/filters/%s", id), nil, nil, nil)
+}
diff --git a/renderer/model.go b/renderer/model.go
index 6c3ba90..aed444a 100644
--- a/renderer/model.go
+++ b/renderer/model.go
@@ -127,3 +127,8 @@ type SettingsData struct {
Settings *model.Settings
PostFormats []model.PostFormat
}
+
+type FiltersData struct {
+ *CommonData
+ Filters []*mastodon.Filter
+}
diff --git a/renderer/renderer.go b/renderer/renderer.go
index 3d4685f..067632f 100644
--- a/renderer/renderer.go
+++ b/renderer/renderer.go
@@ -29,6 +29,7 @@ const (
RetweetedByPage = "retweetedby.tmpl"
SearchPage = "search.tmpl"
SettingsPage = "settings.tmpl"
+ FiltersPage = "filters.tmpl"
)
type TemplateData struct {
diff --git a/service/service.go b/service/service.go
index ce689fd..8149f33 100644
--- a/service/service.go
+++ b/service/service.go
@@ -641,6 +641,22 @@ func (s *service) SettingsPage(c *client) (err error) {
return s.renderer.Render(rCtx, c, renderer.SettingsPage, data)
}
+func (svc *service) FiltersPage(c *client) (err error) {
+ filters, err := c.GetFilters(ctx)
+ if err != nil {
+ return
+ }
+
+ commonData := svc.getCommonData(c, "filters")
+ data := &renderer.FiltersData{
+ CommonData: commonData,
+ Filters: filters,
+ }
+
+ rCtx := getRendererContext(c)
+ return svc.renderer.Render(rCtx, c, renderer.FiltersPage, data)
+}
+
func (s *service) SingleInstance() (instance string, ok bool) {
if len(s.singleInstance) > 0 {
instance = s.singleInstance
@@ -908,3 +924,12 @@ func (s *service) UnBookmark(c *client, id string) (err error) {
_, err = c.Unbookmark(ctx, id)
return
}
+
+func (svc *service) Filter(c *client, phrase string, wholeWord bool) (err error) {
+ fctx := []string{"home", "notifications", "public", "thread"}
+ return c.AddFilter(ctx, phrase, fctx, true, wholeWord, nil)
+}
+
+func (svc *service) UnFilter(c *client, id string) (err error) {
+ return c.RemoveFilter(ctx, id)
+}
diff --git a/service/transport.go b/service/transport.go
index 096b44e..1180f6c 100644
--- a/service/transport.go
+++ b/service/transport.go
@@ -262,6 +262,10 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
return s.SettingsPage(c)
}, SESSION, HTML)
+ filtersPage := handle(func(c *client) error {
+ return s.FiltersPage(c)
+ }, SESSION, HTML)
+
signin := handle(func(c *client) error {
instance := c.Req.FormValue("instance")
url, sid, err := s.NewSession(instance)
@@ -589,6 +593,27 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
return nil
}, CSRF, HTML)
+ filter := handle(func(c *client) error {
+ phrase := c.Req.FormValue("phrase")
+ wholeWord := c.Req.FormValue("whole_word") == "true"
+ err := s.Filter(c, phrase, wholeWord)
+ if err != nil {
+ return err
+ }
+ redirect(c, c.Req.FormValue("referrer"))
+ return nil
+ }, CSRF, HTML)
+
+ unFilter := handle(func(c *client) error {
+ id, _ := mux.Vars(c.Req)["id"]
+ err := s.UnFilter(c, id)
+ if err != nil {
+ return err
+ }
+ redirect(c, c.Req.FormValue("referrer"))
+ return nil
+ }, CSRF, HTML)
+
signout := handle(func(c *client) error {
s.Signout(c)
setSessionCookie(c, "", 0)
@@ -648,6 +673,7 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
r.HandleFunc("/emojis", emojisPage).Methods(http.MethodGet)
r.HandleFunc("/search", searchPage).Methods(http.MethodGet)
r.HandleFunc("/settings", settingsPage).Methods(http.MethodGet)
+ r.HandleFunc("/filters", filtersPage).Methods(http.MethodGet)
r.HandleFunc("/signin", signin).Methods(http.MethodPost)
r.HandleFunc("/oauth_callback", oauthCallback).Methods(http.MethodGet)
r.HandleFunc("/post", post).Methods(http.MethodPost)
@@ -673,6 +699,8 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {
r.HandleFunc("/notifications/read", readNotifications).Methods(http.MethodPost)
r.HandleFunc("/bookmark/{id}", bookmark).Methods(http.MethodPost)
r.HandleFunc("/unbookmark/{id}", unBookmark).Methods(http.MethodPost)
+ r.HandleFunc("/filter", filter).Methods(http.MethodPost)
+ r.HandleFunc("/unfilter/{id}", unFilter).Methods(http.MethodPost)
r.HandleFunc("/signout", signout).Methods(http.MethodPost)
r.HandleFunc("/fluoride/like/{id}", fLike).Methods(http.MethodPost)
r.HandleFunc("/fluoride/unlike/{id}", fUnlike).Methods(http.MethodPost)
diff --git a/static/style.css b/static/style.css
index 1921f5e..b51b439 100644
--- a/static/style.css
+++ b/static/style.css
@@ -552,6 +552,14 @@ kbd {
font-size: 10pt;
}
+.filters {
+ margin: 10px 0;
+}
+
+.filters td {
+ padding: 2px 4px;
+}
+
.dark {
background-color: #222222;
background-image: none;
diff --git a/templates/filters.tmpl b/templates/filters.tmpl
new file mode 100644
index 0000000..ef7c024
--- /dev/null
+++ b/templates/filters.tmpl
@@ -0,0 +1,40 @@
+{{with .Data}}
+{{template "header.tmpl" (WithContext .CommonData $.Ctx)}}
+<div class="page-title"> Filters </div>
+
+{{if .Filters}}
+<table class="filters">
+ {{range .Filters}}
+ <tr>
+ <td> {{.Phrase}}{{if not .WholeWord}}*{{end}} </td>
+ <td>
+ <form action="/unfilter/{{.ID}}" method="POST">
+ <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}">
+ <input type="hidden" name="referrer" value="{{$.Ctx.Referrer}}">
+ <button type="submit"> Delete </button>
+ </form>
+ </td>
+ </tr>
+ {{end}}
+</table>
+{{else}}
+ <div class="filters"> No filters added </div>
+{{end}}
+
+<div class="page-title"> Add filter </div>
+<form action="/filter" method="POST">
+ <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}">
+ <input type="hidden" name="referrer" value="{{$.Ctx.Referrer}}">
+ <span class="settings-form-field">
+ <label for="phrase"> Phrase </label>
+ <input id="phrase" name="phrase" required>
+ </span>
+ <span class="settings-form-field">
+ <input id="whole-word" name="whole_word" type="checkbox" value="true" checked>
+ <label for="whole-word"> Whole word </label>
+ </span>
+ <button type="submit"> Add </button>
+</form>
+
+{{template "footer.tmpl"}}
+{{end}}
diff --git a/templates/user.tmpl b/templates/user.tmpl
index af6a8d1..7aaefa7 100644
--- a/templates/user.tmpl
+++ b/templates/user.tmpl
@@ -119,6 +119,7 @@
{{end}}
<div>
<a href="/usersearch/{{.User.ID}}"> search statuses </a>
+ {{if .IsCurrent}} - <a href="/filters"> filters </a> {{end}}
</div>
</div>
<div class="user-profile-decription">