Compare commits

..

No commits in common. "215930e3b950ceb224fc7a4f8dd652963f35d333" and "44f8a72a76fd8f0f5634c667d6c6ebb34e965fe2" have entirely different histories.

18 changed files with 109 additions and 415 deletions

10
README
View File

@ -9,15 +9,9 @@ Changes (localhost_custom fork):
- Is personal fork, no recommended for public use (use as local frontend) - Is personal fork, no recommended for public use (use as local frontend)
- Add reactions for pleroma (includes custom emojis input) - Add reactions for pleroma (includes custom emojis input)
- tag timeline - tag timeline
- true remote timeline
- visible edited post time - visible edited post time
- visible quoted post (status in status) - visible quoted post (status in status)
- visible profile banner in spoiler - visible profile banner in spoiler
- add media description submit (very woozy way, but it works as possible)
- add schedule status
- add language input form
- add expiry status
- add support for send poll (hardcoded to 20 options)
- hide boosts in spoiler - hide boosts in spoiler
- hide NSFW content and attachments in spoiler - hide NSFW content and attachments in spoiler
- some micro visual changes - some micro visual changes
@ -55,7 +49,3 @@ this software. If not, see http://creativecommons.org/publicdomain/zero/1.0/.
[1] https://pleroma.social [1] https://pleroma.social
Related projects:
https://codeberg.org/SimpleWeb/SimpleerTube (archived, but it works)
https://codeberg.org/ManeraKai/simplytranslate

View File

@ -64,16 +64,8 @@ type MetaData struct {
} }
type FederationInfo struct { type FederationInfo struct {
Enabled bool `json:"enabled"` Enabled bool `json:enabled"`
MrfPolicies []string `json:"mrf_policies"` MrfPolicies []string `json:mrf_policies"`
MrfSimple MRFSimple `json:"mrf_simple"`
}
type MRFSimple struct {
Reject []string `json:"reject"`
MediaNsfw []string `json:"media_nsfw"`
FederatedTimelineRemoval []string `json:"federated_timeline_removal"`
RejectDeletes []string `json:"reject_deletes"`
} }

View File

@ -210,18 +210,6 @@ type Toot struct {
SpoilerText string `json:"spoiler_text"` SpoilerText string `json:"spoiler_text"`
Visibility string `json:"visibility"` Visibility string `json:"visibility"`
ContentType string `json:"content_type"` ContentType string `json:"content_type"`
Language string `json:"language"`
ExpiresIn int `json:"expires_in"`
ScheduledAt string `json:"scheduled_at"`
Poll TootPoll `json:"poll"`
}
// TootPoll is struct to poll in post status.
type TootPoll struct {
ExpiresIn int `json:"expires_in"`
HideTotals bool `json:"hide_totals"`
Multiple bool `json:"multiple"`
Options []string `json:"options"`
} }
// Mention hold information for mention. // Mention hold information for mention.
@ -256,11 +244,6 @@ type Attachment struct {
TextURL string `json:"text_url"` TextURL string `json:"text_url"`
Description string `json:"description"` Description string `json:"description"`
Meta AttachmentMeta `json:"meta"` Meta AttachmentMeta `json:"meta"`
//Misskey fields
Comment string `json:"comment"`
ThumbnailUrl string `json:"thumbnailUrl"`
Sensitive bool `json:"isSensitive"`
} }
// AttachmentMeta holds information for attachment metadata. // AttachmentMeta holds information for attachment metadata.

View File

@ -1,27 +0,0 @@
package mastodon
type MisskeyStatus struct {
ID string `json:"id"`
User AccountMisskey `json:"user"`
Renote *MisskeyStatus `json:"renote"`
CreatedAt CreatedAt `json:"createdAt"`
Visibility string `json:"visibility"`
CW string `json:"cw"`
URL string `json:"url"`
Text string `json:"text"`
Files []Attachment `json:"files"`
RenoteCount int64 `json:"renoteCount"`
RepliesCount int64 `json:"repliesCount"`
Reactions map[string]int `json:"reactions"`
}
type AccountMisskey struct {
ID string `json:"id"`
Host string `json:"host"`
Name string `json:"name"`
Username string `json:"username"`
AvatarURL string `json:"avatarUrl"`
IsBot bool `json:"isBot"`
}

View File

@ -11,7 +11,7 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"encoding/json" "encoding/json"
"strconv" "path"
"strings" "strings"
) )
@ -230,169 +230,48 @@ func (c *Client) GetTimelineHome(ctx context.Context, pg *Pagination) ([]*Status
return statuses, nil return statuses, nil
} }
type RemoteTimelineInstance struct {
http.Client
}
// TrueRemoteTimeline get public timeline from remote Mastodon API compatible instance directly // TrueRemoteTimeline get public timeline from remote Mastodon API compatible instance directly
func (c *Client) TrueRemoteTimeline(ctx context.Context, instance string, instance_type string, pg *Pagination) ([]*Status, error) { func (c *Client) TrueRemoteTimeline(ctx context.Context, instance string, pg *Pagination) ([]*Status, error) {
var httpclient RemoteTimelineInstance
var publicstatuses []*Status var publicstatuses []*Status
var instanceParams []string
instanceParams = strings.Split(instance, ":")[1:]
instance = strings.TrimSpace(strings.Split(instance, ":")[0])
params := url.Values{} params := url.Values{}
params.Set("local", "true") params.Set("local", "true")
if pg != nil { if pg != nil {
params = pg.setValues(params) params = pg.setValues(params)
} }
u, err := url.Parse("https://" + instance)
if err != nil {
return nil, err
}
u.Path = path.Join(u.Path, "/api/v1/timelines/public")
perform := url.URL{ req, err := http.NewRequest(http.MethodGet, u.String(), strings.NewReader(params.Encode()))
Scheme: "https", if err != nil {
Host: instance, return nil, err
} }
var paramval []string
withFiles := "false"
withReplies := "false"
globalTimeline := false
for _, instanceParam := range instanceParams {
switch instanceParam {
case "withFiles":
withFiles = "true"
params.Set("only_media", "true")
case "withReplies":
withReplies = "true"
case "remote":
globalTimeline = true
params.Set(instanceParam, "true")
default:
paramval = strings.Split(instanceParam, "=")
if len(paramval) == 2 {
params.Set(paramval[0], paramval[1])
} else {
params.Set(instanceParam, "true")
}
}
}
var method string
var ContentType string
var bytesAttach []byte
switch instance_type {
case "misskey":
if globalTimeline {
perform.Path = "api/notes/global-timeline"
} else {
perform.Path = "api/notes/local-timeline"
}
perform.RawQuery = ""
method = http.MethodPost
ContentType = "application/json"
bytesAttach = []byte(fmt.Sprintf(
`{"limit":20,"withRenotes":false, "withReplies": %s, "withFiles": %s}`,
withReplies, withFiles))
if pg != nil {
if pg.MaxID != "" {
bytesAttach = []byte(fmt.Sprintf(
`{"limit": %s,"withRenotes": false,"untilId":"%s", "withReplies": %s, "withFiles": %s}`,
strconv.Itoa(int(pg.Limit)), pg.MaxID, withReplies, withFiles))
}
}
default:
if globalTimeline {
params.Set("local", "false")
}
perform.RawQuery = params.Encode()
perform.Path = "api/v1/timelines/public"
method = http.MethodGet
ContentType = "application/x-www-form-urlencoded"
bytesAttach = []byte("")
}
req, err := http.NewRequest(method, perform.String(), bytes.NewBuffer(bytesAttach))
req = req.WithContext(ctx) req = req.WithContext(ctx)
req.Header.Set("Content-Type", ContentType) req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("User-Agent", "Bloat") resp, err := httpclient.Do(req)
client := http.Client{} fmt.Println(req)
resp, err := client.Do(req) fmt.Println(resp)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return nil, parseAPIError("Can't get remote timeline for " + instance + ", try select another type instance. Error" , resp) return nil, parseAPIError("bad request", resp)
} }
switch instance_type {
case "misskey":
var misskeyData []MisskeyStatus
err = json.NewDecoder(resp.Body).Decode(&misskeyData)
if err != nil {
return nil, err
}
for _, statusMisskey := range misskeyData {
var status Status
status.ID = statusMisskey.ID
if statusMisskey.Renote != nil {
// small handle for strange reblogs in misskey
// handle as quoted post because akkoma/pleroma makes same
var quote Status
quote.ID = statusMisskey.Renote.ID
quote.Content = strings.Replace(statusMisskey.Renote.Text, "\n", "<br>", -1)
status.Pleroma.Quote = &quote
}
status.Account.ID = statusMisskey.User.ID
status.Account.DisplayName = statusMisskey.User.Name
if statusMisskey.User.Host != "" {
status.Account.Acct = statusMisskey.User.Username + "@" + statusMisskey.User.Host
} else {
status.Account.Acct = statusMisskey.User.Username
}
status.Account.Username = statusMisskey.User.Username
status.Account.Avatar = statusMisskey.User.AvatarURL
status.CreatedAt = statusMisskey.CreatedAt
status.Visibility = statusMisskey.Visibility
status.Content = strings.Replace(statusMisskey.Text, "\n", "<br>", -1) + "<br><br>"
for reaction, count := range statusMisskey.Reactions { // woozyface
if reaction == "❤" {
status.FavouritesCount = int64(count)
continue
}
status.Content = status.Content + "[" + reaction + strconv.Itoa(count) + "]"
}
status.MediaAttachments = statusMisskey.Files
for idx, attach := range statusMisskey.Files {
status.MediaAttachments[idx].Type = strings.Split(attach.Type, "/")[0]
status.MediaAttachments[idx].Description = strings.Replace(attach.Comment, "\n", "<br>", -1)
status.MediaAttachments[idx].PreviewURL = attach.ThumbnailUrl
status.MediaAttachments[idx].RemoteURL = attach.URL
status.MediaAttachments[idx].TextURL = attach.URL
if status.Sensitive == false {
if attach.Sensitive { // mark status as NSFW if any attachment marked as NSFW
status.Sensitive = true
}
}
}
if statusMisskey.CW != "" {
status.Sensitive = true
status.SpoilerText = statusMisskey.CW
}
status.RepliesCount = statusMisskey.RepliesCount
status.ReblogsCount = statusMisskey.RenoteCount
status.Account.Bot = statusMisskey.User.IsBot
status.URL = "https://" + instance + "/notes/" + statusMisskey.ID
publicstatuses = append(publicstatuses, &status)
}
case "friendica":
err = json.NewDecoder(resp.Body).Decode(&publicstatuses) err = json.NewDecoder(resp.Body).Decode(&publicstatuses)
fmt.Println(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, status := range publicstatuses {
status.URL = status.URI // Fix federate URL
}
default:
err = json.NewDecoder(resp.Body).Decode(&publicstatuses)
if err != nil {
return nil, err
}
}
return publicstatuses, nil return publicstatuses, nil
} }
@ -479,28 +358,6 @@ func (c *Client) PostStatus(ctx context.Context, toot *Toot) (*Status, error) {
if toot.ContentType != "" { if toot.ContentType != "" {
params.Set("content_type", toot.ContentType) params.Set("content_type", toot.ContentType)
} }
if toot.Language != "" {
params.Set("language", toot.Language)
}
if toot.ExpiresIn >= 3600 {
params.Set("expires_in", fmt.Sprint(toot.ExpiresIn))
}
if toot.ScheduledAt != "" {
params.Set("scheduled_at", toot.ScheduledAt)
}
if len(toot.Poll.Options) > 2 {
for _, option := range toot.Poll.Options {
params.Add("poll[options][]", string(option))
}
params.Set("poll[expires_in]", strconv.Itoa(toot.Poll.ExpiresIn))
if toot.Poll.Multiple {
params.Set("poll[multiple]", "true")
}
if toot.Poll.HideTotals {
params.Set("poll[hide_totals]", "true")
}
}
var status Status var status Status
if toot.Edit != "" { if toot.Edit != "" {
err := c.doAPI(ctx, http.MethodPut, fmt.Sprintf("/api/v1/statuses/%s", toot.Edit), params, &status, nil) err := c.doAPI(ctx, http.MethodPut, fmt.Sprintf("/api/v1/statuses/%s", toot.Edit), params, &status, nil)
@ -551,7 +408,7 @@ func (c *Client) Search(ctx context.Context, q string, qType string, limit int,
return &results, nil return &results, nil
} }
func (c *Client) UploadMediaFromMultipartFileHeader(ctx context.Context, fh *multipart.FileHeader, descr string) (*Attachment, error) { func (c *Client) UploadMediaFromMultipartFileHeader(ctx context.Context, fh *multipart.FileHeader) (*Attachment, error) {
f, err := fh.Open() f, err := fh.Open()
if err != nil { if err != nil {
return nil, err return nil, err
@ -561,7 +418,7 @@ func (c *Client) UploadMediaFromMultipartFileHeader(ctx context.Context, fh *mul
var buf bytes.Buffer var buf bytes.Buffer
mw := multipart.NewWriter(&buf) mw := multipart.NewWriter(&buf)
fname := filepath.Base(fh.Filename) fname := filepath.Base(fh.Filename)
err = mw.WriteField("description", descr) err = mw.WriteField("description", fname)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -19,5 +19,4 @@ type ReplyContext struct {
ReplySpoiler string ReplySpoiler string
ReplyContent string ReplyContent string
ForceVisibility bool ForceVisibility bool
ReplyLanguage string
} }

View File

@ -59,7 +59,6 @@ type TimelineData struct {
Title string Title string
Type string Type string
Instance string Instance string
InstanceType string
Statuses []*mastodon.Status Statuses []*mastodon.Status
NextLink string NextLink string
PrevLink string PrevLink string
@ -110,6 +109,11 @@ type UserData struct {
NextLink string NextLink string
} }
type UserEditData struct {
*CommonData
User *mastodon.Account
}
type UserSearchData struct { type UserSearchData struct {
*CommonData *CommonData
User *mastodon.Account User *mastodon.Account

View File

@ -7,7 +7,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"fmt"
"bloat/mastodon" "bloat/mastodon"
) )
@ -35,6 +34,7 @@ const (
RetweetedByPage = "retweetedby.tmpl" RetweetedByPage = "retweetedby.tmpl"
SearchPage = "search.tmpl" SearchPage = "search.tmpl"
SettingsPage = "settings.tmpl" SettingsPage = "settings.tmpl"
UserEditPage = "useredit.tmpl"
FiltersPage = "filters.tmpl" FiltersPage = "filters.tmpl"
ProfilePage = "profile.tmpl" ProfilePage = "profile.tmpl"
MutePage = "mute.tmpl" MutePage = "mute.tmpl"
@ -68,22 +68,6 @@ func emojiFilter(content string, emojis []mastodon.Emoji) string {
return strings.NewReplacer(replacements...).Replace(content) return strings.NewReplacer(replacements...).Replace(content)
} }
func generatePollOptions() string {
var pollbuilder string
for i := 0; i < 20; i++ {
pollbuilder = pollbuilder + `<div><input id="` + fmt.Sprintf("poll-option-%d", i) + `" name="` + fmt.Sprintf("poll-option-%d", i) + `"></div>`
}
return pollbuilder
}
func generateMediaDescrForm() string {
var mediadescrbuilder string
for i := 0; i < 20; i++ {
mediadescrbuilder = mediadescrbuilder + `<div><textarea rows="2" id="` + fmt.Sprintf("media-descr-%d", i) + `" name="` + fmt.Sprintf("media-descr-%d", i) + `"></textarea></div>`
}
return mediadescrbuilder
}
var quoteRE = regexp.MustCompile("(?mU)(^|> *|\n)(&gt;.*)(<br|$)") var quoteRE = regexp.MustCompile("(?mU)(^|> *|\n)(&gt;.*)(<br|$)")
func statusContentFilter(content string, emojis []mastodon.Emoji, mentions []mastodon.Mention) string { func statusContentFilter(content string, emojis []mastodon.Emoji, mentions []mastodon.Mention) string {
@ -175,8 +159,6 @@ func NewRenderer(templateGlobPattern string) (r *renderer, err error) {
t, err = t.Funcs(template.FuncMap{ t, err = t.Funcs(template.FuncMap{
"EmojiFilter": emojiFilter, "EmojiFilter": emojiFilter,
"Allowed_emoji_page": allowed_emoji_page, "Allowed_emoji_page": allowed_emoji_page,
"GeneratePollOptions": generatePollOptions,
"GenerateMediaDescrForm": generateMediaDescrForm,
"StatusContentFilter": statusContentFilter, "StatusContentFilter": statusContentFilter,
"DisplayInteractionCount": displayInteractionCount, "DisplayInteractionCount": displayInteractionCount,
"TimeSince": timeSince, "TimeSince": timeSince,

View File

@ -117,7 +117,7 @@ func (s *service) NavPage(c *client) (err error) {
} }
func (s *service) TimelinePage(c *client, tType, instance, listId, maxID, func (s *service) TimelinePage(c *client, tType, instance, listId, maxID,
minID string, tag string, instance_type string) (err error) { minID string, tag string) (err error) {
var nextLink, prevLink, title string var nextLink, prevLink, title string
var statuses []*mastodon.Status var statuses []*mastodon.Status
@ -159,18 +159,10 @@ func (s *service) TimelinePage(c *client, tType, instance, listId, maxID,
title = "Remote Timeline" title = "Remote Timeline"
case "tremote": case "tremote":
if len(instance) > 0 { if len(instance) > 0 {
if instance_type == "" { statuses, err = c.TrueRemoteTimeline(c.ctx, instance, &pg)
instance_type = "mastodon-compatible"
}
statuses, err = c.TrueRemoteTimeline(c.ctx, instance, instance_type, &pg)
if err != nil { if err != nil {
return err return err
} }
v := make(url.Values)
v.Set("max_id", statuses[len(statuses)-1].ID)
v.Set("instance", instance)
v.Set("instance_type", instance_type)
nextLink = "/timeline/" + tType + "?" + v.Encode()
} }
title = "True Remote Timeline" title = "True Remote Timeline"
case "twkn": case "twkn":
@ -215,10 +207,6 @@ func (s *service) TimelinePage(c *client, tType, instance, listId, maxID,
} }
statuses[i].Pleroma.Reactions = reactions statuses[i].Pleroma.Reactions = reactions
} }
if tType == "tremote" {
statuses[i].URL = fmt.Sprintf("/search?q=%s&type=statuses", statuses[i].URL)
}
} }
if (len(maxID) > 0 || len(minID) > 0) && len(statuses) > 0 { if (len(maxID) > 0 || len(minID) > 0) && len(statuses) > 0 {
@ -227,9 +215,6 @@ func (s *service) TimelinePage(c *client, tType, instance, listId, maxID,
if len(instance) > 0 { if len(instance) > 0 {
v.Set("instance", instance) v.Set("instance", instance)
} }
if len(instance_type) > 0 {
v.Set("instance_type", instance_type)
}
if len(tag) > 0 { if len(tag) > 0 {
v.Set("tag", tag) v.Set("tag", tag)
} }
@ -242,13 +227,9 @@ func (s *service) TimelinePage(c *client, tType, instance, listId, maxID,
if len(minID) > 0 || (len(pg.MaxID) > 0 && len(statuses) == 20) { if len(minID) > 0 || (len(pg.MaxID) > 0 && len(statuses) == 20) {
v := make(url.Values) v := make(url.Values)
v.Set("max_id", pg.MaxID) v.Set("max_id", pg.MaxID)
v.Set("max_id", statuses[len(statuses)-1].ID)
if len(instance) > 0 { if len(instance) > 0 {
v.Set("instance", instance) v.Set("instance", instance)
} }
if len(instance_type) > 0 {
v.Set("instance_type", instance_type)
}
if len(tag) > 0 { if len(tag) > 0 {
v.Set("tag", tag) v.Set("tag", tag)
} }
@ -263,7 +244,6 @@ func (s *service) TimelinePage(c *client, tType, instance, listId, maxID,
Title: title, Title: title,
Type: tType, Type: tType,
Instance: instance, Instance: instance,
InstanceType: instance_type,
Statuses: statuses, Statuses: statuses,
NextLink: nextLink, NextLink: nextLink,
PrevLink: prevLink, PrevLink: prevLink,
@ -382,8 +362,6 @@ func (s *service) ThreadPage(c *client, id string, reply bool) (err error) {
visibility = c.s.Settings.DefaultVisibility visibility = c.s.Settings.DefaultVisibility
} }
replyLanguage := status.Language
pctx = model.PostContext{ pctx = model.PostContext{
DefaultVisibility: visibility, DefaultVisibility: visibility,
DefaultFormat: c.s.Settings.DefaultFormat, DefaultFormat: c.s.Settings.DefaultFormat,
@ -394,7 +372,6 @@ func (s *service) ThreadPage(c *client, id string, reply bool) (err error) {
ReplySpoiler: spoilerText, ReplySpoiler: spoilerText,
ReplyContent: content, ReplyContent: content,
ForceVisibility: isDirect, ForceVisibility: isDirect,
ReplyLanguage: replyLanguage,
}, },
} }
} }
@ -859,6 +836,19 @@ func (s *service) SearchPage(c *client,
return s.renderer.Render(c.rctx, c.w, renderer.SearchPage, data) return s.renderer.Render(c.rctx, c.w, renderer.SearchPage, data)
} }
func (s *service) UserEditPage(c *client) (err error) {
cdata := s.cdata(c, "useredit", 0, 0, "")
u, err := c.GetAccountCurrentUser(c.ctx)
if err != nil {
return
}
data := &renderer.UserEditData{
CommonData: cdata,
User: u,
}
return s.renderer.Render(c.rctx, c.w, renderer.UserEditPage, data)
}
func (s *service) SettingsPage(c *client) (err error) { func (s *service) SettingsPage(c *client) (err error) {
cdata := s.cdata(c, "settings", 0, 0, "") cdata := s.cdata(c, "settings", 0, 0, "")
data := &renderer.SettingsData{ data := &renderer.SettingsData{
@ -998,28 +988,17 @@ func (s *service) Signout(c *client) (err error) {
func (s *service) Post(c *client, content string, replyToID string, func (s *service) Post(c *client, content string, replyToID string,
format string, visibility string, isNSFW bool, spoilerText string, format string, visibility string, isNSFW bool, spoilerText string,
files []*multipart.FileHeader, edit string, language string, expiresIn int, scheduledAt string, files []*multipart.FileHeader, edit string) (id string, err error) {
pollOptions []string, pollExpiresIn int, pollHideTotals bool, pollMultiple bool,
mediaDescription []string) (id string, err error) {
var mediaIDs []string var mediaIDs []string
for idx, f := range files { for _, f := range files {
a, err := c.UploadMediaFromMultipartFileHeader(c.ctx, f, mediaDescription[idx]) a, err := c.UploadMediaFromMultipartFileHeader(c.ctx, f)
if err != nil { if err != nil {
return "", err return "", err
} }
mediaIDs = append(mediaIDs, a.ID) mediaIDs = append(mediaIDs, a.ID)
} }
expiresIn = expiresIn * 3600
pollTweet := mastodon.TootPoll{
ExpiresIn: pollExpiresIn,
Options: pollOptions,
Multiple: pollMultiple,
HideTotals: pollHideTotals,
}
tweet := &mastodon.Toot{ tweet := &mastodon.Toot{
SpoilerText: spoilerText, SpoilerText: spoilerText,
Status: content, Status: content,
@ -1029,12 +1008,7 @@ func (s *service) Post(c *client, content string, replyToID string,
Visibility: visibility, Visibility: visibility,
Sensitive: isNSFW, Sensitive: isNSFW,
Edit: edit, Edit: edit,
Language: language,
ExpiresIn: expiresIn, // pleroma compatible
ScheduledAt: scheduledAt,
Poll: pollTweet,
} }
st, err := c.PostStatus(c.ctx, tweet) st, err := c.PostStatus(c.ctx, tweet)
if err != nil { if err != nil {
return return

View File

@ -139,8 +139,7 @@ func NewHandler(s *service, verbose bool, staticDir string) http.Handler {
maxID := q.Get("max_id") maxID := q.Get("max_id")
minID := q.Get("min_id") minID := q.Get("min_id")
tag := q.Get("tag") tag := q.Get("tag")
instance_type := q.Get("instance_type") return s.TimelinePage(c, tType, instance, list, maxID, minID, tag)
return s.TimelinePage(c, tType, instance, list, maxID, minID, tag, instance_type)
}, SESSION, HTML) }, SESSION, HTML)
defaultTimelinePage := handle(func(c *client) error { defaultTimelinePage := handle(func(c *client) error {
@ -314,41 +313,9 @@ func NewHandler(s *service, verbose bool, staticDir string) http.Handler {
isNSFW := c.r.FormValue("is_nsfw") == "true" isNSFW := c.r.FormValue("is_nsfw") == "true"
quickReply := c.r.FormValue("quickreply") == "true" quickReply := c.r.FormValue("quickreply") == "true"
files := c.r.MultipartForm.File["attachments"] files := c.r.MultipartForm.File["attachments"]
var mediaDescription []string
for i := 0; i < len(files); i++ {
v := c.r.FormValue(fmt.Sprintf("media-descr-%d", i))
mediaDescription = append(mediaDescription, v)
}
edit := c.r.FormValue("edit-status-id") edit := c.r.FormValue("edit-status-id")
language := c.r.FormValue("lang-code")
expiresIn, err := strconv.Atoi(c.r.FormValue("expires-in"))
if err != nil {
return err
}
scheduledAt := c.r.FormValue("scheduled")
if scheduledAt != "" {
scheduled, err := time.Parse("2006-01-02T15:04", scheduledAt)
if err != nil {
return err
}
scheduledAt = string(scheduled.UTC().Format(time.RFC3339))
}
var pollOptions []string
for i := 0; i < 20; i++ {
v := c.r.FormValue(fmt.Sprintf("poll-option-%d", i))
if len(v) == 0 {
continue
}
pollOptions = append(pollOptions, v)
}
pollExpiresIn, err := strconv.Atoi(c.r.FormValue("poll-expires-in"))
if err != nil {
return err
}
pollHideTotals := c.r.FormValue("poll-hide-totals") == "true"
pollMultiple := c.r.FormValue("poll-is-multiple") == "true"
id, err := s.Post(c, content, replyToID, format, visibility, isNSFW, spoilerText, files, edit, language, expiresIn, scheduledAt, pollOptions, pollExpiresIn, pollHideTotals, pollMultiple, mediaDescription) id, err := s.Post(c, content, replyToID, format, visibility, isNSFW, spoilerText, files, edit)
if err != nil { if err != nil {
return err return err
} }
@ -770,9 +737,6 @@ func NewHandler(s *service, verbose bool, staticDir string) http.Handler {
id, _ := mux.Vars(c.r)["id"] id, _ := mux.Vars(c.r)["id"]
q := c.r.URL.Query() q := c.r.URL.Query()
uid := q.Get("uid") uid := q.Get("uid")
if uid == "" {
uid = c.r.FormValue("uid")
}
err := s.ListAddUser(c, id, uid) err := s.ListAddUser(c, id, uid)
if err != nil { if err != nil {
return err return err

View File

@ -73,16 +73,10 @@
<div> <div>
{{if .Instance.Pleroma}} {{if .Instance.Pleroma}}
<div class="federation-enabled"> Federation enabled: {{.Instance.Pleroma.MetaData.Federation.Enabled}} </div> <div class="federation-enabled"> Federation enabled: {{.Instance.Pleroma.MetaData.Federation.Enabled}} </div>
<h6> MRF Policies </h6> <div class="mrf-policies"> MRF Policies </div>
{{range .Instance.Pleroma.MetaData.Federation.MrfPolicies}}{{.}}<br>{{end}} {{range .Instance.Pleroma.MetaData.Federation.MrfPolicies}}
<h6> Rejected instances </h6> {{.}}<br>
{{range .Instance.Pleroma.MetaData.Federation.MrfSimple.Reject}}{{.}}<br>{{end}} {{end}}
<h6> Force NSFW instances </h6>
{{range .Instance.Pleroma.MetaData.Federation.MrfSimple.MediaNsfw}}{{.}}<br>{{end}}
<h6> Hiden from federated timeline instances </h6>
{{range .Instance.Pleroma.MetaData.Federation.MrfSimple.FederatedTimelineRemoval}}{{.}}<br>{{end}}
<h6> Rejected deletion activities </h6>
{{range .Instance.Pleroma.MetaData.Federation.MrfSimple.RejectDeletes}}{{.}}<br>{{end}}
{{end}} {{end}}
<details> <details>
<summary>Current peers ({{.Instance.Stats.DomainCount}})</summary> <summary>Current peers ({{.Instance.Stats.DomainCount}})</summary>

View File

@ -37,14 +37,6 @@
</span> </span>
<button type="submit"> Search </button> <button type="submit"> Search </button>
</form> </form>
<div class="page-title"> Add user by their ID </div>
<span>some times it can works, some times not</span>
<form class="user-list-action" action="/list/{{$.Data.List.ID}}/adduser?" method="POST">
<input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}">
<input type="hidden" name="referrer" value="{{$.Ctx.Referrer}}">
<input name="uid">
<button type="submit"> Add </button>
</form>
{{if .Q}} {{if .Q}}
{{if .SearchAccounts}} {{if .SearchAccounts}}

View File

@ -43,32 +43,11 @@
<div> <div>
<span class="post-form-field"> <span class="post-form-field">
<input id="post-file-picker" type="file" name="attachments" multiple accesskey="A" title="Attachments (A)"> <input id="post-file-picker" type="file" name="attachments" multiple accesskey="A" title="Attachments (A)">
<details><summary> Descriptions for media attachments </summary>
{{GenerateMediaDescrForm | Raw}}
</details>
</span> </span>
</div> </div>
<button type="submit" accesskey="P" title="Post (P)"> Post </button> <button type="submit" accesskey="P" title="Post (P)"> Post </button>
<button type="reset" title="Reset"> Reset </button> <button type="reset" title="Reset"> Reset </button>
<input id="edit-status-id" name="edit-status-id" placeholder="Input Status ID for edit" title="Edit ID"> <input id="edit-status-id" name="edit-status-id" placeholder="Input Status ID for edit" title="Edit ID">
<input id="lang-code" name="lang-code" placeholder="lang" title="Post language (ISO 639) [en, ru, etc..] Default: none" size="4" value="{{if .ReplyContext}}{{.ReplyContext.ReplyLanguage}}{{end}}">
<input type="number" id="expires-in" name="expires-in" title="Post autodeleted after hour(s)" min="0" value="0" size="4">
<input type="datetime-local" id="scheduled" name="scheduled" step=300 title="Schedule your status (timezone UTC+0)">
<details><summary>Poll</summary>
<div>
<input type="number" id="poll-expires-in" name="poll-expires-in" min="1" value="300">
<label for="poll-expires-in"> Expires in (secs) </label>
</div>
<div>
<input type="checkbox" id="poll-hide-totals" name="poll-hide-totals" value="true">
<label for="poll-hide-totals"> Hide vote counts? </label>
</div>
<div>
<input type="checkbox" id="poll-is-multiple" name="poll-is-multiple" value="true">
<label for="poll-is_multiple "> Allow multiple choice? </label>
</div>
<div>{{GeneratePollOptions | Raw}}</div>
</details>
</form> </form>
{{end}} {{end}}

View File

@ -13,17 +13,6 @@
<a class="img-link" href="/user/{{.ID}}"> <a class="img-link" href="/user/{{.ID}}">
<div class="status-uname">{{.Acct}}</div> <div class="status-uname">{{.Acct}}</div>
</a> </a>
<div class="status-uregdate">registered/federated {{TimeSince .CreatedAt}}</div>
{{if eq (len .Note) 0}}
<div class="status-ubio">empty bio!</div>
{{end}}
{{if not .StatusesCount}}
<div class="status-ubio">no statuses!</div>
{{end}}
{{if .Moved}}
<div class="status-umv">Moved to <a href="/user/{{.Moved.ID}}">{{.Moved.Acct}}</a></div>
{{end}}
<div class="status-ufls">follows: {{.FollowingCount}} followers: {{.FollowersCount}}</div>
</div> </div>
<form class="d-inline" action="/accept/{{.ID}}" method="post" target="_self"> <form class="d-inline" action="/accept/{{.ID}}" method="post" target="_self">
<input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}"> <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}">

View File

@ -227,11 +227,6 @@
</form> </form>
{{end}} {{end}}
<div class="status-action-container"> <div class="status-action-container">
{{if eq (printf "%.7s" .URL) "/search"}}
<div class="status-action">
<a href="{{.URL}}" target="_blank">Federate!</a>
</div>
{{else}}
<div class="status-action"> <div class="status-action">
<a href="/thread/{{.ID}}?reply=true#status-{{.ID}}"> <a href="/thread/{{.ID}}?reply=true#status-{{.ID}}">
reply reply
@ -242,7 +237,6 @@
{{end}} {{end}}
</a> </a>
</div> </div>
{{end}}
<div class="status-action"> <div class="status-action">
{{$rt := "retweet"}} {{if .Reblogged}} {{$rt = "unretweet"}} {{end}} {{$rt := "retweet"}} {{if .Reblogged}} {{$rt = "unretweet"}} {{end}}
<form class="status-retweet" data-action="{{$rt}}" action="/{{$rt}}/{{.ID}}" method="post" target="_self"> <form class="status-retweet" data-action="{{$rt}}" action="/{{$rt}}/{{.ID}}" method="post" target="_self">

View File

@ -13,27 +13,6 @@
</span> </span>
<button type="submit"> Submit </button> <button type="submit"> Submit </button>
</form> </form>
{{if eq .Instance ""}}
<a href="/timeline/tremote"> True remote timeline viewer </a>
{{else}}
{{end}}
{{end}}
{{if eq .Type "tremote"}}
<form class="search-form" action="/timeline/tremote" method="GET">
<span class="post-form-field">
<label for="instance"> Instance </label>
<input id="instance" name="instance" value="{{.Instance}}" placeholder="example.com:optional_param">
<select id="instance_type" name="instance_type" title="Select instance type">
<option value="mastodon-compatible" default>Mastodon compatible</option>
<option value="misskey"{{if eq .InstanceType "misskey"}}selected{{end}}>Misskey</option>
<option value="friendica"{{if eq .InstanceType "friendica"}}selected{{end}}>Friendica</option>
</select>
</span>
<button type="submit"> Submit </button>
</form>
{{if eq .Instance ""}}
<span>Possible params:<br>withFiles - show posts only with media<br>remote - try fetch their global timeline<br>You can combine params: withFile:remote:some_other_param</span>
{{end}}
{{end}} {{end}}
{{if eq .Type "tag"}} {{if eq .Type "tag"}}
<form class="search-form" action="/timeline/tag" method="GET"> <form class="search-form" action="/timeline/tag" method="GET">

View File

@ -114,6 +114,7 @@
- <a href="/user/{{.User.ID}}/mutes"> mutes </a> - <a href="/user/{{.User.ID}}/mutes"> mutes </a>
- <a href="/user/{{.User.ID}}/blocks"> blocks </a> - <a href="/user/{{.User.ID}}/blocks"> blocks </a>
{{if .User.Locked}}- <a href="/user/{{.User.ID}}/requests"> requests </a>{{end}} {{if .User.Locked}}- <a href="/user/{{.User.ID}}/requests"> requests </a>{{end}}
- <a href="/useredit"> edit </a>
</div> </div>
{{end}} {{end}}
<div> <div>

48
templates/useredit.tmpl Normal file
View File

@ -0,0 +1,48 @@
{{with .Data}}
{{template "header.tmpl" (WithContext .CommonData $.Ctx)}}
<div class="page-title"> User settings </div>
<form id="user-settings-form" action="/useredit" method="POST">
<input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}">
<input type="hidden" name="referrer" value="{{$.Ctx.Referrer}}">
<input type="hidden" name="id" value="{{.User.ID}}">
<div class="settings-form-field">
<input id="display-name" name="display-name" value="{{HTML .User.DisplayName}}">
<label for="display-name"> Your display name </label>
</div>
<div class="settings-form-field">
<label for="note"> Your bio: </label>
</div>
<div>
<textarea id="note" name="note" cols="80" rows="8"></textarea>
<div>
<details><summary>Current bio for copypaste</summary>{{.User.Note | Raw}}</details>
</div>
</div>
<div class="settings-form-field" title="Whether manual approval of follow requests is required.">
<input id="locked" name="locked" type="checkbox" value="true" {{if .User.Locked}}checked{{end}}>
<label for="locked"> Locked user? </label>
</div>
<div class="settings-form-field" title="Whether manual approval of follow requests is required.">
<input id="bot" name="bot" type="checkbox" value="true" {{if .User.Bot}}checked{{end}}>
<label for="bot"> User is bot? </label>
</div>
{{if .User.MastodonAccount}}
{{else}}
<div class="page-title"> Pleroma settings </div>
<div class="settings-form-field">
<input id="accepts-chat-messages" name="hide-attachments" type="checkbox" value="true" {{if .User.Pleroma.AcceptsChatMessages}}checked{{end}}>
<label for="accepts-chat-messages"> Allow receive chat messages (bloat not support chats feature) </label>
</div>
<div class="settings-form-field">
<input id="hide-favourites" name="hide-favourites" type="checkbox" value="true" {{if .User.Pleroma.HideFavourites}}checked{{end}}>
<label for="hide-favourites"> User's favorites timeline will be hidden </label>
</div>
{{end}}
<button type="submit"> Save </button>
</form>
{{template "footer.tmpl"}}
{{end}}