diff --git a/.gitignore b/.gitignore
index 037bea6..516f77b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,2 @@
bloat
database
-bloat.def.conf
diff --git a/INSTALL b/INSTALL
index 032f612..c73fd78 100644
--- a/INSTALL
+++ b/INSTALL
@@ -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
diff --git a/Makefile b/Makefile
index 4231015..1b32268 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/README b/README
index 9c76da1..b00592f 100644
--- a/README
+++ b/README
@@ -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
+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)
}
- defer f.Close()
-
- info, err := f.Stat()
- if err != nil {
- return
+ if lastErr == nil {
+ lastErr = errors.New("invalid config file")
}
-
- if info.IsDir() {
- return nil, errors.New("invalid config file")
- }
-
- return Parse(f)
+ return nil, lastErr
}
diff --git a/main.go b/main.go
index cac5eee..3b5ccba 100644
--- a/main.go
+++ b/main.go
@@ -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 ``
+}
+
func emojiFilter(content string, emojis []mastodon.Emoji) string {
var replacements []string
- var r string
for _, e := range emojis {
- r = fmt.Sprintf("",
- 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)(>.*)(
0 {
- content = spoiler + "
" + content
+ content = spoiler + "
" + content
}
+ content = quoteRE.ReplaceAllString(content, `$1$2$3`)
+ var replacements []string
for _, e := range emojis {
- r = fmt.Sprintf("",
- 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,18 +517,18 @@ img.emoji {
margin-top: 6px;
}
-.notification-title-container {
+.page-title-container {
margin: 8px 0;
}
+.page-refresh {
+ margin-right: 8px;
+}
+
.notification-text {
vertical-align: middle;
}
-.notification-refresh {
- margin-right: 8px;
-}
-
.notification-read {
display: inline-block;
}
@@ -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 @@