diff --git a/mastodon/mastodon.go b/mastodon/mastodon.go index 04afeb4..0bab2d0 100644 --- a/mastodon/mastodon.go +++ b/mastodon/mastodon.go @@ -256,6 +256,11 @@ type Attachment struct { TextURL string `json:"text_url"` Description string `json:"description"` Meta AttachmentMeta `json:"meta"` + + //Misskey fields + Comment string `json:"comment"` + ThumbnailUrl string `json:"thumbnailUrl"` + Sensitive bool `json:"isSensitive"` } // AttachmentMeta holds information for attachment metadata. diff --git a/mastodon/status.go b/mastodon/status.go index 8ef7cde..e77b99b 100644 --- a/mastodon/status.go +++ b/mastodon/status.go @@ -12,6 +12,7 @@ import ( "time" "encoding/json" "strconv" + "strings" ) type StatusPleroma struct { @@ -91,6 +92,28 @@ type Status struct { RetweetedByID string `json:"retweeted_by_id"` } +type MisskeyStatus struct { + ID string `json:"id"` + User AccountMisskey `json:"user"` + 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"` + Name string `json:"name"` + Username string `json:"username"` + AvatarURL string `json:"avatarUrl"` + IsBot bool `json:"isBot"` +} + // Context hold information for mastodon context. type Context struct { Ancestors []*Status `json:"ancestors"` @@ -229,16 +252,15 @@ func (c *Client) GetTimelineHome(ctx context.Context, pg *Pagination) ([]*Status return statuses, nil } -type RemoteTimelineInstance struct { - http.Client -} - // TrueRemoteTimeline get public timeline from remote Mastodon API compatible instance directly -func (c *Client) TrueRemoteTimeline(ctx context.Context, instance string, pg *Pagination) ([]*Status, error) { - var httpclient RemoteTimelineInstance +func (c *Client) TrueRemoteTimeline(ctx context.Context, instance string, instance_type string, pg *Pagination) ([]*Status, error) { var publicstatuses []*Status + var instanceParams []string + instanceParams = strings.Split(instance, ":")[1:] + instance = strings.Split(instance, ":")[0] params := url.Values{} params.Set("local", "true") + if pg != nil { params = pg.setValues(params) } @@ -246,32 +268,115 @@ func (c *Client) TrueRemoteTimeline(ctx context.Context, instance string, pg *Pa perform := url.URL{ Scheme: "https", Host: instance, - Path: "api/v1/timelines/public", RawQuery: params.Encode(), } - - req, err := http.NewRequest(http.MethodGet, perform.String(), nil) - if err != nil { - return nil, err + withFiles := "false" + withReplies := "false" + for _, instanceParam := range instanceParams { + switch instanceParam { + case "withFiles": + withFiles = "true" + case "withReplies": + withReplies = "true" + default: + params.Set(instanceParam, "true") + } } + + var method string + var ContentType string + var bytesAttach []byte + switch instance_type { + case "misskey": + 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: + 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.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Content-Type", ContentType) req.Header.Set("User-Agent", "Bloat") - resp, err := httpclient.Do(req) + client := http.Client{} + resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return nil, parseAPIError("bad request", resp) + return nil, parseAPIError("Can't get remote timeline for " + instance + ", try select another type instance. Error" , resp) } - - err = json.NewDecoder(resp.Body).Decode(&publicstatuses) - if err != nil { - return nil, err + 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 + status.Account.ID = statusMisskey.User.ID + status.Account.DisplayName = statusMisskey.User.Name + 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", "
", -1) + "

" + 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", "
", -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) + } + default: + err = json.NewDecoder(resp.Body).Decode(&publicstatuses) + if err != nil { + return nil, err + } } - return publicstatuses, nil } diff --git a/renderer/model.go b/renderer/model.go index 4c31001..1096bd9 100644 --- a/renderer/model.go +++ b/renderer/model.go @@ -59,6 +59,7 @@ type TimelineData struct { Title string Type string Instance string + InstanceType string Statuses []*mastodon.Status NextLink string PrevLink string diff --git a/service/service.go b/service/service.go index 3c9b8a4..fdac742 100644 --- a/service/service.go +++ b/service/service.go @@ -117,7 +117,7 @@ func (s *service) NavPage(c *client) (err error) { } func (s *service) TimelinePage(c *client, tType, instance, listId, maxID, - minID string, tag string) (err error) { + minID string, tag string, instance_type string) (err error) { var nextLink, prevLink, title string var statuses []*mastodon.Status @@ -159,13 +159,17 @@ func (s *service) TimelinePage(c *client, tType, instance, listId, maxID, title = "Remote Timeline" case "tremote": if len(instance) > 0 { - statuses, err = c.TrueRemoteTimeline(c.ctx, instance, &pg) + if instance_type == "" { + instance_type = "mastodon-compatible" + } + statuses, err = c.TrueRemoteTimeline(c.ctx, instance, instance_type, &pg) if err != nil { 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" @@ -223,6 +227,9 @@ func (s *service) TimelinePage(c *client, tType, instance, listId, maxID, if len(instance) > 0 { v.Set("instance", instance) } + if len(instance_type) > 0 { + v.Set("instance_type", instance_type) + } if len(tag) > 0 { v.Set("tag", tag) } @@ -235,9 +242,13 @@ func (s *service) TimelinePage(c *client, tType, instance, listId, maxID, if len(minID) > 0 || (len(pg.MaxID) > 0 && len(statuses) == 20) { v := make(url.Values) v.Set("max_id", pg.MaxID) + v.Set("max_id", statuses[len(statuses)-1].ID) if len(instance) > 0 { v.Set("instance", instance) } + if len(instance_type) > 0 { + v.Set("instance_type", instance_type) + } if len(tag) > 0 { v.Set("tag", tag) } @@ -252,6 +263,7 @@ func (s *service) TimelinePage(c *client, tType, instance, listId, maxID, Title: title, Type: tType, Instance: instance, + InstanceType: instance_type, Statuses: statuses, NextLink: nextLink, PrevLink: prevLink, diff --git a/service/transport.go b/service/transport.go index 034b83d..2d82035 100644 --- a/service/transport.go +++ b/service/transport.go @@ -139,7 +139,8 @@ func NewHandler(s *service, verbose bool, staticDir string) http.Handler { maxID := q.Get("max_id") minID := q.Get("min_id") tag := q.Get("tag") - return s.TimelinePage(c, tType, instance, list, maxID, minID, tag) + instance_type := q.Get("instance_type") + return s.TimelinePage(c, tType, instance, list, maxID, minID, tag, instance_type) }, SESSION, HTML) defaultTimelinePage := handle(func(c *client) error { diff --git a/templates/timeline.tmpl b/templates/timeline.tmpl index ffa85f9..102f0c6 100644 --- a/templates/timeline.tmpl +++ b/templates/timeline.tmpl @@ -14,10 +14,23 @@ {{if eq .Instance ""}} + True remote timeline viewer {{else}} - Look via True remote timeline viewer (works with mastodon API compatible instances) {{end}} {{end}} +{{if eq .Type "tremote"}} +
+ + + + + + +
+{{end}} {{if eq .Type "tag"}}