diff options
| author | r <r@freesoftwareextremist.com> | 2022-01-27 12:05:15 +0000 | 
|---|---|---|
| committer | r <r@freesoftwareextremist.com> | 2022-01-27 12:05:15 +0000 | 
| commit | b8c0133bcd5e7f1d4063ad992949cc19d18e7aad (patch) | |
| tree | 5322b83d194c9ffcb498627bdd16175f9508d87d | |
| parent | 54c42455f393c5ae8ebdb19884d40ebd9a18f755 (diff) | |
| parent | 4ef5e0daf285f41850c9ac53b0322d85fbf5eaec (diff) | |
| download | bloat-b8c0133bcd5e7f1d4063ad992949cc19d18e7aad.tar.gz bloat-b8c0133bcd5e7f1d4063ad992949cc19d18e7aad.zip | |
Merge branch 'master' into absolute_fluoride
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | INSTALL | 12 | ||||
| -rw-r--r-- | Makefile | 13 | ||||
| -rw-r--r-- | README | 4 | ||||
| -rw-r--r-- | config/config.go | 39 | ||||
| -rw-r--r-- | main.go | 19 | ||||
| -rw-r--r-- | mastodon/accounts.go | 8 | ||||
| -rw-r--r-- | mastodon/helper.go | 23 | ||||
| -rw-r--r-- | mastodon/notification.go | 5 | ||||
| -rw-r--r-- | mastodon/status.go | 15 | ||||
| -rw-r--r-- | model/settings.go | 46 | ||||
| -rw-r--r-- | renderer/renderer.go | 25 | ||||
| -rw-r--r-- | service/service.go | 19 | ||||
| -rw-r--r-- | service/transport.go | 32 | ||||
| -rw-r--r-- | static/fluoride.js | 12 | ||||
| -rw-r--r-- | static/style.css | 14 | ||||
| -rw-r--r-- | templates/about.tmpl | 2 | ||||
| -rw-r--r-- | templates/nav.tmpl | 2 | ||||
| -rw-r--r-- | templates/notification.tmpl | 8 | ||||
| -rw-r--r-- | templates/requestlist.tmpl | 2 | ||||
| -rw-r--r-- | templates/settings.tmpl | 5 | ||||
| -rw-r--r-- | templates/status.tmpl | 8 | ||||
| -rw-r--r-- | templates/thread.tmpl | 4 | ||||
| -rw-r--r-- | templates/timeline.tmpl | 5 | ||||
| -rw-r--r-- | templates/user.tmpl | 8 | ||||
| -rw-r--r-- | templates/userlist.tmpl | 2 | ||||
| -rw-r--r-- | templates/usersearch.tmpl | 2 | ||||
| -rw-r--r-- | util/getopt.go | 122 | ||||
| -rw-r--r-- | util/rand.go | 20 | 
29 files changed, 214 insertions, 263 deletions
| @@ -1,3 +1,2 @@  bloat  database -bloat.def.conf @@ -15,12 +15,12 @@ This will perform a system wide installation of bloat. By default, it will  install the binary in /usr/local/bin and data files in /usr/local/share/bloat.  You can change these paths by editing the Makefile. -3. Edit and copy the config file -Edit the generated config file to you liking and then copy it to the default -config location. Comments in the config file describe what each config value  -does. For most cases, you only need to change the value of "client_website". -$ $EDITOR bloat.def.conf -# cp bloat.def.conf /etc/bloat.conf +3. Edit the config file +bloat looks for a file named bloat.conf in the working directory and +/etc/bloat in that order. You can also specify another file by using the -f +flag. Comments in the config file describe what each config value does. For +most cases, you only need to change the value of "client_website". +# $EDITOR /etc/bloat.conf  4. Create database directory  Create a directory to store session information. Optionally, create a user @@ -14,17 +14,11 @@ SRC=main.go		\  	service/*.go 	\  	util/*.go 	\ -all: bloat bloat.def.conf +all: bloat  bloat: $(SRC) $(TMPL)  	$(GO) build $(GOFLAGS) -o bloat main.go -bloat.def.conf: -	sed -e "s%=database%=/var/bloat%g" \ -		-e "s%=templates%=$(SHAREPATH)/templates%g" \ -		-e "s%=static%=$(SHAREPATH)/static%g" \ -		< bloat.conf > bloat.def.conf -  install: bloat  	mkdir -p $(DESTDIR)$(BINPATH) \  		$(DESTDIR)$(SHAREPATH)/templates \ @@ -35,6 +29,10 @@ install: bloat  	chmod 0644 $(DESTDIR)$(SHAREPATH)/templates/*  	cp -r static/* $(DESTDIR)$(SHAREPATH)/static  	chmod 0644 $(DESTDIR)$(SHAREPATH)/static/* +	sed -e "s%=database%=/var/bloat%g" \ +		-e "s%=templates%=$(SHAREPATH)/templates%g" \ +		-e "s%=static%=$(SHAREPATH)/static%g" \ +		< bloat.conf > /etc/bloat.conf  uninstall:  	rm -f $(DESTDIR)$(BINPATH)/bloat @@ -42,4 +40,3 @@ uninstall:  clean:   	rm -f bloat -	rm -f bloat.def.conf @@ -15,11 +15,11 @@ Building and Installation:  Typing make will build the binary   $ make -Edit the provided config file. See the bloat.conf file for more details.   +Edit the default config file. See the bloat.conf file for more details.  $ ed bloat.conf  Run the binary -$ ./bloat -f bloat.conf +$ ./bloat  You can now access the frontend at http://127.0.0.1:8080, which is the default  listen address. See the INSTALL file for more details. diff --git a/config/config.go b/config/config.go index 8678f52..bbb327c 100644 --- a/config/config.go +++ b/config/config.go @@ -108,21 +108,30 @@ func Parse(r io.Reader) (c *config, err error) {  	return  } -func ParseFile(file string) (c *config, err error) { -	f, err := os.Open(file) -	if err != nil { -		return -	} -	defer f.Close() - -	info, err := f.Stat() -	if err != nil { -		return +func ParseFiles(files []string) (c *config, err error) { +	var lastErr error +	for _, file := range files { +		f, err := os.Open(file) +		if err != nil { +			lastErr = err +			if os.IsNotExist(err) { +				continue +			} +			return nil, err +		} +		defer f.Close() +		info, err := f.Stat() +		if err != nil { +			lastErr = err +			return nil, err +		} +		if info.IsDir() { +			continue +		} +		return Parse(f)  	} - -	if info.IsDir() { -		return nil, errors.New("invalid config file") +	if lastErr == nil { +		lastErr = errors.New("invalid config file")  	} - -	return Parse(f) +	return nil, lastErr  } @@ -2,6 +2,7 @@ package main  import (  	"errors" +	"flag"  	"fmt"  	"log"  	"net/http" @@ -17,7 +18,7 @@ import (  )  var ( -	configFile = "/etc/bloat.conf" +	configFiles = []string{"bloat.conf", "/etc/bloat.conf"}  )  func errExit(err error) { @@ -26,19 +27,13 @@ func errExit(err error) {  }  func main() { -	opts, _, err := util.Getopts(os.Args, "f:") -	if err != nil { -		errExit(err) -	} +	configFile := flag.String("f", "", "config file") +	flag.Parse() -	for _, opt := range opts { -		switch opt.Option { -		case 'f': -			configFile = opt.Value -		} +	if len(*configFile) > 0 { +		configFiles = []string{*configFile}  	} - -	config, err := config.ParseFile(configFile) +	config, err := config.ParseFiles(configFiles)  	if err != nil {  		errExit(err)  	} diff --git a/mastodon/accounts.go b/mastodon/accounts.go index 694e672..df0a3b7 100644 --- a/mastodon/accounts.go +++ b/mastodon/accounts.go @@ -243,9 +243,13 @@ func (c *Client) AccountUnblock(ctx context.Context, id string) (*Relationship,  }  // AccountMute mute the account. -func (c *Client) AccountMute(ctx context.Context, id string) (*Relationship, error) { +func (c *Client) AccountMute(ctx context.Context, id string, notifications *bool) (*Relationship, error) { +	params := url.Values{} +	if notifications != nil { +		params.Set("notifications", strconv.FormatBool(*notifications)) +	}  	var relationship Relationship -	err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/accounts/%s/mute", url.PathEscape(string(id))), nil, &relationship, nil) +	err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/accounts/%s/mute", url.PathEscape(string(id))), params, &relationship, nil)  	if err != nil {  		return nil, err  	} diff --git a/mastodon/helper.go b/mastodon/helper.go index 05af20f..cb0013d 100644 --- a/mastodon/helper.go +++ b/mastodon/helper.go @@ -3,12 +3,28 @@ package mastodon  import (  	"encoding/base64"  	"encoding/json" -	"errors"  	"fmt"  	"net/http"  	"os"  ) +type Error struct { +	code int +	err  string +} + +func (e Error) Error() string { +	return e.err +} + +func (e Error) IsAuthError() bool { +	switch e.code { +	case http.StatusForbidden, http.StatusUnauthorized: +		return true +	} +	return false +} +  // Base64EncodeFileName returns the base64 data URI format string of the file with the file name.  func Base64EncodeFileName(filename string) (string, error) {  	file, err := os.Open(filename) @@ -51,5 +67,8 @@ func parseAPIError(prefix string, resp *http.Response) error {  		errMsg = fmt.Sprintf("%s: %s", errMsg, e.Error)  	} -	return errors.New(errMsg) +	return Error{ +		code: resp.StatusCode, +		err:  errMsg, +	}  } diff --git a/mastodon/notification.go b/mastodon/notification.go index 656e6a1..e94f901 100644 --- a/mastodon/notification.go +++ b/mastodon/notification.go @@ -23,9 +23,12 @@ type Notification struct {  }  // GetNotifications return notifications. -func (c *Client) GetNotifications(ctx context.Context, pg *Pagination, excludes []string) ([]*Notification, error) { +func (c *Client) GetNotifications(ctx context.Context, pg *Pagination, includes, excludes []string) ([]*Notification, error) {  	var notifications []*Notification  	params := url.Values{} +	for _, include := range includes { +		params.Add("include_types[]", include) +	}  	for _, exclude := range excludes {  		params.Add("exclude_types[]", exclude)  	} diff --git a/mastodon/status.go b/mastodon/status.go index 80e7e0e..8b148b3 100644 --- a/mastodon/status.go +++ b/mastodon/status.go @@ -19,6 +19,19 @@ type ReplyInfo struct {  	Number int    `json:"number"`  } +type CreatedAt struct { +	time.Time +} + +func (t *CreatedAt) UnmarshalJSON(d []byte) error { +	// Special case to handle retweets from GNU Social +	// which returns empty string ("") in created_at +	if len(d) == 2 && string(d) == `""` { +		return nil +	} +	return t.Time.UnmarshalJSON(d) +} +  // Status is struct to hold status.  type Status struct {  	ID                 string       `json:"id"` @@ -29,7 +42,7 @@ type Status struct {  	InReplyToAccountID interface{}  `json:"in_reply_to_account_id"`  	Reblog             *Status      `json:"reblog"`  	Content            string       `json:"content"` -	CreatedAt          time.Time    `json:"created_at"` +	CreatedAt          CreatedAt    `json:"created_at"`  	Emojis             []Emoji      `json:"emojis"`  	RepliesCount       int64        `json:"replies_count"`  	ReblogsCount       int64        `json:"reblogs_count"` diff --git a/model/settings.go b/model/settings.go index c4e8aec..1f83c75 100644 --- a/model/settings.go +++ b/model/settings.go @@ -1,31 +1,33 @@  package model  type Settings struct { -	DefaultVisibility    string `json:"default_visibility"` -	DefaultFormat        string `json:"default_format"` -	CopyScope            bool   `json:"copy_scope"` -	ThreadInNewTab       bool   `json:"thread_in_new_tab"` -	HideAttachments      bool   `json:"hide_attachments"` -	MaskNSFW             bool   `json:"mask_nfsw"` -	NotificationInterval int    `json:"notifications_interval"` -	FluorideMode         bool   `json:"fluoride_mode"` -	DarkMode             bool   `json:"dark_mode"` -	AntiDopamineMode     bool   `json:"anti_dopamine_mode"` -	CSS                  string `json:"css"` +	DefaultVisibility     string `json:"default_visibility"` +	DefaultFormat         string `json:"default_format"` +	CopyScope             bool   `json:"copy_scope"` +	ThreadInNewTab        bool   `json:"thread_in_new_tab"` +	HideAttachments       bool   `json:"hide_attachments"` +	MaskNSFW              bool   `json:"mask_nfsw"` +	NotificationInterval  int    `json:"notifications_interval"` +	FluorideMode          bool   `json:"fluoride_mode"` +	DarkMode              bool   `json:"dark_mode"` +	AntiDopamineMode      bool   `json:"anti_dopamine_mode"` +	HideUnsupportedNotifs bool   `json:"hide_unsupported_notifs"` +	CSS                   string `json:"css"`  }  func NewSettings() *Settings {  	return &Settings{ -		DefaultVisibility:    "public", -		DefaultFormat:        "", -		CopyScope:            true, -		ThreadInNewTab:       false, -		HideAttachments:      false, -		MaskNSFW:             true, -		NotificationInterval: 0, -		FluorideMode:         false, -		DarkMode:             false, -		AntiDopamineMode:     false, -		CSS:                  "", +		DefaultVisibility:     "public", +		DefaultFormat:         "", +		CopyScope:             true, +		ThreadInNewTab:        false, +		HideAttachments:       false, +		MaskNSFW:              true, +		NotificationInterval:  0, +		FluorideMode:          false, +		DarkMode:              false, +		AntiDopamineMode:      false, +		HideUnsupportedNotifs: false, +		CSS:                   "",  	}  } diff --git a/renderer/renderer.go b/renderer/renderer.go index 6c9877a..f50e185 100644 --- a/renderer/renderer.go +++ b/renderer/renderer.go @@ -1,8 +1,8 @@  package renderer  import ( -	"fmt"  	"io" +	"regexp"  	"strconv"  	"strings"  	"text/template" @@ -39,29 +39,28 @@ type TemplateData struct {  	Ctx  *Context  } +func emojiHTML(e mastodon.Emoji, height string) string { +	return `<img class="emoji" src="` + e.URL + `" alt=":` + e.ShortCode + `:" title=":` + e.ShortCode + `:" height="` + height + `"/>` +} +  func emojiFilter(content string, emojis []mastodon.Emoji) string {  	var replacements []string -	var r string  	for _, e := range emojis { -		r = fmt.Sprintf("<img class=\"emoji\" src=\"%s\" alt=\":%s:\" title=\":%s:\" height=\"24\" />", -			e.URL, e.ShortCode, e.ShortCode) -		replacements = append(replacements, ":"+e.ShortCode+":", r) +		replacements = append(replacements, ":"+e.ShortCode+":", emojiHTML(e, "24"))  	}  	return strings.NewReplacer(replacements...).Replace(content)  } -func statusContentFilter(spoiler string, content string, -	emojis []mastodon.Emoji, mentions []mastodon.Mention) string { +var quoteRE = regexp.MustCompile("(?mU)(^|> *|\n)(>.*)(<br|$)") -	var replacements []string -	var r string +func statusContentFilter(spoiler, content string, emojis []mastodon.Emoji, mentions []mastodon.Mention) string {  	if len(spoiler) > 0 { -		content = spoiler + "<br />" + content +		content = spoiler + "<br/>" + content  	} +	content = quoteRE.ReplaceAllString(content, `$1<span class="quote">$2</span>$3`) +	var replacements []string  	for _, e := range emojis { -		r = fmt.Sprintf("<img class=\"emoji\" src=\"%s\" alt=\":%s:\" title=\":%s:\" height=\"32\" />", -			e.URL, e.ShortCode, e.ShortCode) -		replacements = append(replacements, ":"+e.ShortCode+":", r) +		replacements = append(replacements, ":"+e.ShortCode+":", emojiHTML(e, "32"))  	}  	for _, m := range mentions {  		replacements = append(replacements, `"`+m.URL+`"`, `"/user/`+m.ID+`" title="@`+m.Acct+`"`) diff --git a/service/service.go b/service/service.go index a846322..c56114c 100644 --- a/service/service.go +++ b/service/service.go @@ -114,7 +114,8 @@ func (s *service) ErrorPage(c *client, err error, retry bool) error {  	var sessionErr bool  	if err != nil {  		errStr = err.Error() -		if err == errInvalidSession || err == errInvalidCSRFToken { +		if me, ok := err.(mastodon.Error); ok && me.IsAuthError() || +			err == errInvalidSession || err == errInvalidCSRFToken {  			sessionErr = true  		}  	} @@ -417,18 +418,24 @@ func (s *service) NotificationPage(c *client, maxID string,  	var nextLink string  	var unreadCount int  	var readID string -	var excludes []string +	var includes, excludes []string  	var pg = mastodon.Pagination{  		MaxID: maxID,  		MinID: minID,  		Limit: 20,  	} +	if c.s.Settings.HideUnsupportedNotifs { +		// Explicitly include the supported types. +		// For now, only Pleroma supports this option, Mastadon +		// will simply ignore the unknown params. +		includes = []string{"follow", "follow_request", "mention", "reblog", "favourite"} +	}  	if c.s.Settings.AntiDopamineMode { -		excludes = []string{"follow", "favourite", "reblog"} +		excludes = append(excludes, "follow", "favourite", "reblog")  	} -	notifications, err := c.GetNotifications(c.ctx, &pg, excludes) +	notifications, err := c.GetNotifications(c.ctx, &pg, includes, excludes)  	if err != nil {  		return  	} @@ -914,8 +921,8 @@ func (s *service) Reject(c *client, id string) (err error) {  	return c.FollowRequestReject(c.ctx, id)  } -func (s *service) Mute(c *client, id string) (err error) { -	_, err = c.AccountMute(c.ctx, id) +func (s *service) Mute(c *client, id string, notifications *bool) (err error) { +	_, err = c.AccountMute(c.ctx, id, notifications)  	return  } diff --git a/service/transport.go b/service/transport.go index a022b02..02e6106 100644 --- a/service/transport.go +++ b/service/transport.go @@ -415,7 +415,13 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {  	mute := handle(func(c *client) error {  		id, _ := mux.Vars(c.r)["id"] -		err := s.Mute(c, id) +		q := c.r.URL.Query() +		var notifications *bool +		if r, ok := q["notifications"]; ok && len(r) > 0 { +			notifications = new(bool) +			*notifications = r[0] == "true" +		} +		err := s.Mute(c, id, notifications)  		if err != nil {  			return err  		} @@ -484,20 +490,22 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler {  		fluorideMode := c.r.FormValue("fluoride_mode") == "true"  		darkMode := c.r.FormValue("dark_mode") == "true"  		antiDopamineMode := c.r.FormValue("anti_dopamine_mode") == "true" +		hideUnsupportedNotifs := c.r.FormValue("hide_unsupported_notifs") == "true"  		css := c.r.FormValue("css")  		settings := &model.Settings{ -			DefaultVisibility:    visibility, -			DefaultFormat:        format, -			CopyScope:            copyScope, -			ThreadInNewTab:       threadInNewTab, -			HideAttachments:      hideAttachments, -			MaskNSFW:             maskNSFW, -			NotificationInterval: ni, -			FluorideMode:         fluorideMode, -			DarkMode:             darkMode, -			AntiDopamineMode:     antiDopamineMode, -			CSS:                  css, +			DefaultVisibility:     visibility, +			DefaultFormat:         format, +			CopyScope:             copyScope, +			ThreadInNewTab:        threadInNewTab, +			HideAttachments:       hideAttachments, +			MaskNSFW:              maskNSFW, +			NotificationInterval:  ni, +			FluorideMode:          fluorideMode, +			DarkMode:              darkMode, +			AntiDopamineMode:      antiDopamineMode, +			HideUnsupportedNotifs: hideUnsupportedNotifs, +			CSS:                   css,  		}  		err := s.SaveSettings(c, settings) diff --git a/static/fluoride.js b/static/fluoride.js index c7f3109..279483f 100644 --- a/static/fluoride.js +++ b/static/fluoride.js @@ -298,20 +298,24 @@ function setPos(el, cx, cy, mw, mh) {  }  var imgPrev = null; +var imgX = 0; +var imgY = 0;  function handleImgPreview(a) {  	a.onmouseenter = function(e) {  		var mw = document.documentElement.clientWidth;  		var mh = document.documentElement.clientHeight - 24; +		imgX = e.clientX; +		imgY = e.clientY;  		var img = document.createElement("img");  		img.id = "img-preview";  		img.src = e.target.getAttribute("href");  		img.style["max-width"] = mw + "px";  		img.style["max-height"] = mh + "px"; +		imgPrev = img;  		img.onload = function(e2) { -			setPos(e2.target, e.clientX, e.clientY, mw, mh); +			setPos(imgPrev, imgX, imgY, mw, mh);  		}  		document.body.appendChild(img); -		imgPrev = img;  	}  	a.onmouseleave = function(e) {  		var img = document.getElementById("img-preview"); @@ -324,7 +328,9 @@ function handleImgPreview(a) {  			return;  		var mw = document.documentElement.clientWidth;  		var mh = document.documentElement.clientHeight - 24; -		setPos(imgPrev, e.clientX, e.clientY, mw, mh); +		imgX = e.clientX; +		imgY = e.clientY; +		setPos(imgPrev, imgX, imgY, mw, mh);  	}  } diff --git a/static/style.css b/static/style.css index cd7e98c..4e2a196 100644 --- a/static/style.css +++ b/static/style.css @@ -517,16 +517,16 @@ img.emoji {  	margin-top: 6px;  } -.notification-title-container { +.page-title-container {  	margin: 8px 0;  } -.notification-text { -	vertical-align: middle; +.page-refresh { +	margin-right: 8px;  } -.notification-refresh { -	margin-right: 8px; +.notification-text { +	vertical-align: middle;  }  .notification-read { @@ -575,6 +575,10 @@ kbd {  	position: fixed;  } +.quote { +	color: #789922; +} +  .dark {  	background-color: #222222;  	background-image: none; diff --git a/templates/about.tmpl b/templates/about.tmpl index c0b8418..54316cf 100644 --- a/templates/about.tmpl +++ b/templates/about.tmpl @@ -94,7 +94,7 @@  			<td> <kbd>C</kbd> </td>  		</tr>  		<tr> -			<td> Refresh thread page </td> +			<td> Refresh timeline/thread page </td>  			<td> <kbd>T</kbd> </td>  		</tr>  	</table> diff --git a/templates/nav.tmpl b/templates/nav.tmpl index cbf65c9..ea18a5f 100644 --- a/templates/nav.tmpl +++ b/templates/nav.tmpl @@ -8,7 +8,7 @@  	</div>  	<div class="user-info-details-container">  		<div class="user-info-details-name"> -			<bdi class="status-dname"> {{EmojiFilter .User.DisplayName .User.Emojis}} </bdi>   +			<bdi class="status-dname"> {{EmojiFilter (html .User.DisplayName) .User.Emojis}} </bdi>    			<a class="nav-link" href="/user/{{.User.ID}}" accesskey="0" title="User profile (0)">  				<span class="status-uname"> @{{.User.Acct}} </span>  			</a> diff --git a/templates/notification.tmpl b/templates/notification.tmpl index 2778ef0..a7f88a7 100644 --- a/templates/notification.tmpl +++ b/templates/notification.tmpl @@ -1,13 +1,13 @@  {{with .Data}}  {{template "header.tmpl" (WithContext .CommonData $.Ctx)}} -<div class="notification-title-container"> +<div class="page-title-container">  	<span class="page-title">  		Notifications  		{{if and (not $.Ctx.AntiDopamineMode) (gt .UnreadCount 0)}}  			({{.UnreadCount }})  		{{end}}  	</span> -	<a class="notification-refresh" href="/notifications" target="_self" accesskey="R" title="Refresh (R)">refresh</a> +	<a class="page-refresh" href="/notifications" target="_self" accesskey="R" title="Refresh (R)">refresh</a>  	{{if .ReadID}}  	<form class="notification-read" action="/notifications/read?max_id={{.ReadID}}" method="post" target="_self">  		<input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}"> @@ -28,7 +28,7 @@  		</div>  		<div class="notification-follow">  			<div class="notification-info-text"> -				<bdi class="status-dname"> {{EmojiFilter .Account.DisplayName .Account.Emojis}} </bdi>   +				<bdi class="status-dname"> {{EmojiFilter (html .Account.DisplayName) .Account.Emojis}} </bdi>    				<span class="notification-text"> followed you -   					<time datetime="{{FormatTimeRFC3339 .CreatedAt}}" title="{{FormatTimeRFC822 .CreatedAt}}">{{TimeSince .CreatedAt}}</time>   				</span> @@ -48,7 +48,7 @@  		</div>  		<div class="notification-follow">  			<div class="notification-info-text"> -				<bdi class="status-dname"> {{EmojiFilter .Account.DisplayName .Account.Emojis}} </bdi>   +				<bdi class="status-dname"> {{EmojiFilter (html .Account.DisplayName) .Account.Emojis}} </bdi>    				<span class="notification-text"> wants to follow you -   					<time datetime="{{FormatTimeRFC3339 .CreatedAt}}" title="{{FormatTimeRFC822 .CreatedAt}}">{{TimeSince .CreatedAt}}</time>   				</span> diff --git a/templates/requestlist.tmpl b/templates/requestlist.tmpl index 8142620..d9b2b0a 100644 --- a/templates/requestlist.tmpl +++ b/templates/requestlist.tmpl @@ -9,7 +9,7 @@  		</div>  		<div class="user-list-name">  			<div> -				<div class="status-dname"> {{EmojiFilter .DisplayName .Emojis}} </div>   +				<div class="status-dname"> {{EmojiFilter (html .DisplayName) .Emojis}} </div>    				<a class="img-link" href="/user/{{.ID}}">  					<div class="status-uname"> @{{.Acct}} </div>  				</a> diff --git a/templates/settings.tmpl b/templates/settings.tmpl index 6bae9c7..ebb0458 100644 --- a/templates/settings.tmpl +++ b/templates/settings.tmpl @@ -62,6 +62,11 @@  		<label for="anti-dopamine-mode"> Enable <abbr title="Remove like/retweet/unread notification count and disable like/retweet/follow notifications">anti-dopamine mode</abbr> </label>  	</div>  	<div class="settings-form-field"> +		<input id="hide-unsupported-notifs" name="hide_unsupported_notifs" type="checkbox" +		value="true" {{if .Settings.HideUnsupportedNotifs}}checked{{end}}> +		<label for="hide-unsupported-notifs"> Hide unsupported notifications </label> +	</div> +	<div class="settings-form-field">  		<input id="dark-mode" name="dark_mode" type="checkbox" value="true" {{if .Settings.DarkMode}}checked{{end}}>  		<label for="dark-mode"> Use dark theme </label>  	</div> diff --git a/templates/status.tmpl b/templates/status.tmpl index a7cc10d..dda1d79 100644 --- a/templates/status.tmpl +++ b/templates/status.tmpl @@ -5,7 +5,7 @@  		<a class="img-link" href="/user/{{.Account.ID}}">  			<img class="status-profile-img" src="{{.Account.Avatar}}" title="@{{.Account.Acct}}" alt="avatar" height="24" />  		</a> -		<bdi class="status-dname"> {{EmojiFilter .Account.DisplayName .Account.Emojis}} </bdi>   +		<bdi class="status-dname"> {{EmojiFilter (html .Account.DisplayName) .Account.Emojis}} </bdi>    		<a href="/user/{{.Account.ID}}">   			<span class="status-uname"> @{{.Account.Acct}} </span>   		</a> @@ -23,7 +23,7 @@  		</div>  		<div class="status">   			<div class="status-name"> -				<bdi class="status-dname"> {{EmojiFilter .Account.DisplayName .Account.Emojis}} </bdi>  +				<bdi class="status-dname"> {{EmojiFilter (html .Account.DisplayName) .Account.Emojis}} </bdi>   				<a href="/user/{{.Account.ID}}">  					<span class="status-uname"> @{{.Account.Acct}} </span>  				</a> @@ -227,8 +227,8 @@  				<div class="status-action status-action-last">  					<a class="status-time" href="{{if not .ShowReplies}}/thread/{{.ID}}{{end}}#status-{{.ID}}"  						{{if $.Ctx.ThreadInNewTab}}target="_blank"{{end}}>  -						<time datetime="{{FormatTimeRFC3339 .CreatedAt}}" title="{{FormatTimeRFC822 .CreatedAt}}">  -							{{TimeSince .CreatedAt}} +						<time datetime="{{FormatTimeRFC3339 .CreatedAt.Time}}" title="{{FormatTimeRFC822 .CreatedAt.Time}}"> +							{{TimeSince .CreatedAt.Time}}  						</time>   					</a>  				</div> diff --git a/templates/thread.tmpl b/templates/thread.tmpl index bb9f14f..d6a1c7d 100644 --- a/templates/thread.tmpl +++ b/templates/thread.tmpl @@ -1,8 +1,8 @@  {{with $s := .Data}}  {{template "header.tmpl" (WithContext .CommonData $.Ctx)}} -<div class="notification-title-container"> +<div class="page-title-container">  	<span class="page-title"> Thread </span> -	<a class="notification-refresh" href="{{$.Ctx.Referrer}}" accesskey="T" title="Refresh (T)">refresh</a> +	<a class="page-refresh" href="{{$.Ctx.Referrer}}" accesskey="T" title="Refresh (T)">refresh</a>  </div>  {{range .Statuses}} diff --git a/templates/timeline.tmpl b/templates/timeline.tmpl index bde050a..38659dc 100644 --- a/templates/timeline.tmpl +++ b/templates/timeline.tmpl @@ -1,6 +1,9 @@  {{with .Data}}  {{template "header.tmpl" (WithContext .CommonData $.Ctx)}} -<div class="page-title"> {{.Title}} </div> +<div class="page-title-container"> +	<span class="page-title"> {{.Title}}  </span> +	<a class="page-refresh" href="{{$.Ctx.Referrer}}" accesskey="T" title="Refresh (T)">refresh</a> +</div>  {{if eq .Type "remote"}}  <form class="search-form" action="/timeline/remote" method="GET"> diff --git a/templates/user.tmpl b/templates/user.tmpl index c7b3164..2532a91 100644 --- a/templates/user.tmpl +++ b/templates/user.tmpl @@ -11,7 +11,7 @@  	</div>  	<div class="user-profile-details-container">  		<div> -			<bdi class="status-dname"> {{EmojiFilter .User.DisplayName .User.Emojis}} </bdi>   +			<bdi class="status-dname"> {{EmojiFilter (html .User.DisplayName) .User.Emojis}} </bdi>    			<span class="status-uname"> @{{.User.Acct}} </span>  			<a class="remote-link" href="{{.User.URL}}" target="_blank" title="remote profile">  				source @@ -83,6 +83,12 @@  				<input type="hidden" name="referrer" value="{{$.Ctx.Referrer}}">  				<input type="submit" value="mute" class="btn-link">  			</form> +			- +			<form class="d-inline" action="/mute/{{.User.ID}}?notifications=false" method="post"> +				<input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}"> +				<input type="hidden" name="referrer" value="{{$.Ctx.Referrer}}"> +				<input type="submit" value="mute (keep notifications)" class="btn-link"> +			</form>  			{{end}}  			{{if .User.Pleroma.Relationship.Following}}   			- diff --git a/templates/userlist.tmpl b/templates/userlist.tmpl index 3f75085..b8e0e5d 100644 --- a/templates/userlist.tmpl +++ b/templates/userlist.tmpl @@ -8,7 +8,7 @@  			</a>  		</div>  		<div class="user-list-name"> -			<div class="status-dname"> {{EmojiFilter .DisplayName .Emojis}} </div>   +			<div class="status-dname"> {{EmojiFilter (html .DisplayName) .Emojis}} </div>    			<a class="img-link" href="/user/{{.ID}}">  				<div class="status-uname"> @{{.Acct}} </div>  			</a> diff --git a/templates/usersearch.tmpl b/templates/usersearch.tmpl index ee84143..e95129c 100644 --- a/templates/usersearch.tmpl +++ b/templates/usersearch.tmpl @@ -1,6 +1,6 @@  {{with .Data}}  {{template "header.tmpl" (WithContext .CommonData $.Ctx)}} -<div class="page-title"> Search {{EmojiFilter .User.DisplayName .User.Emojis}}'s statuses </div> +<div class="page-title"> Search {{EmojiFilter (html .User.DisplayName) .User.Emojis}}'s statuses </div>  <form class="search-form" action="/usersearch/{{.User.ID}}" method="GET">  	<span class="post-form-field> diff --git a/util/getopt.go b/util/getopt.go deleted file mode 100644 index 10926a8..0000000 --- a/util/getopt.go +++ /dev/null @@ -1,122 +0,0 @@ -/* -Copyright 2019 Drew DeVault <sir@cmpwn.com> - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation -and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its contributors -may be used to endorse or promote products derived from this software without -specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -package util - -import ( -	"fmt" -	"os" -) - -// In the case of "-o example", Option is 'o' and "example" is Value. For -// options which do not take an argument, Value is "". -type Option struct { -	Option rune -	Value  string -} - -// This is returned when an unknown option is found in argv, but not in the -// option spec. -type UnknownOptionError rune - -func (e UnknownOptionError) Error() string { -	return fmt.Sprintf("%s: unknown option -%c", os.Args[0], rune(e)) -} - -// This is returned when an option with a mandatory argument is missing that -// argument. -type MissingOptionError rune - -func (e MissingOptionError) Error() string { -	return fmt.Sprintf("%s: expected argument for -%c", os.Args[0], rune(e)) -} - -// Getopts implements a POSIX-compatible options interface. -// -// Returns a slice of options and the index of the first non-option argument. -// -// If an error is returned, you must print it to stderr to be POSIX complaint. -func Getopts(argv []string, spec string) ([]Option, int, error) { -	optmap := make(map[rune]bool) -	runes := []rune(spec) -	for i, rn := range spec { -		if rn == ':' { -			if i == 0 { -				continue -			} -			optmap[runes[i-1]] = true -		} else { -			optmap[rn] = false -		} -	} - -	var ( -		i    int -		opts []Option -	) -	for i = 1; i < len(argv); i++ { -		arg := argv[i] -		runes = []rune(arg) -		if len(arg) == 0 || arg == "-" { -			break -		} -		if arg[0] != '-' { -			break -		} -		if arg == "--" { -			i++ -			break -		} -		for j, opt := range runes[1:] { -			if optopt, ok := optmap[opt]; !ok { -				opts = append(opts, Option{'?', ""}) -				return opts, i, UnknownOptionError(opt) -			} else if optopt { -				if j+1 < len(runes)-1 { -					opts = append(opts, Option{opt, string(runes[j+2:])}) -					break -				} else { -					if i+1 >= len(argv) { -						if len(spec) >= 1 && spec[0] == ':' { -							opts = append(opts, Option{':', string(opt)}) -						} else { -							return opts, i, MissingOptionError(opt) -						} -					} else { -						opts = append(opts, Option{opt, argv[i+1]}) -						i++ -					} -				} -			} else { -				opts = append(opts, Option{opt, ""}) -			} -		} -	} -	return opts, i, nil -} diff --git a/util/rand.go b/util/rand.go index 1e4ec95..90e66a5 100644 --- a/util/rand.go +++ b/util/rand.go @@ -2,24 +2,18 @@ package util  import (  	"crypto/rand" -	"math/big" +	"encoding/base64"  ) -var ( -	runes        = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890") -	runes_length = len(runes) -) +var enc = base64.URLEncoding  func NewRandID(n int) (string, error) { -	data := make([]rune, n) -	for i := range data { -		num, err := rand.Int(rand.Reader, big.NewInt(int64(runes_length))) -		if err != nil { -			return "", err -		} -		data[i] = runes[num.Int64()] +	data := make([]byte, enc.DecodedLen(n)) +	_, err := rand.Read(data) +	if err != nil { +		return "", err  	} -	return string(data), nil +	return enc.EncodeToString(data), nil  }  func NewSessionID() (string, error) { | 
