mirror of
https://gitea.phreedom.club/localhost_frssoft/bloat.git
synced 2024-12-22 11:23:33 +02:00
improve true remote timeline
This commit is contained in:
parent
eedd9d2c5c
commit
0f33427d5b
6 changed files with 160 additions and 23 deletions
|
@ -256,6 +256,11 @@ 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.
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StatusPleroma struct {
|
type StatusPleroma struct {
|
||||||
|
@ -91,6 +92,28 @@ type Status struct {
|
||||||
RetweetedByID string `json:"retweeted_by_id"`
|
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.
|
// Context hold information for mastodon context.
|
||||||
type Context struct {
|
type Context struct {
|
||||||
Ancestors []*Status `json:"ancestors"`
|
Ancestors []*Status `json:"ancestors"`
|
||||||
|
@ -229,16 +252,15 @@ 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, pg *Pagination) ([]*Status, error) {
|
func (c *Client) TrueRemoteTimeline(ctx context.Context, instance string, instance_type string, pg *Pagination) ([]*Status, error) {
|
||||||
var httpclient RemoteTimelineInstance
|
|
||||||
var publicstatuses []*Status
|
var publicstatuses []*Status
|
||||||
|
var instanceParams []string
|
||||||
|
instanceParams = strings.Split(instance, ":")[1:]
|
||||||
|
instance = 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)
|
||||||
}
|
}
|
||||||
|
@ -246,32 +268,115 @@ func (c *Client) TrueRemoteTimeline(ctx context.Context, instance string, pg *Pa
|
||||||
perform := url.URL{
|
perform := url.URL{
|
||||||
Scheme: "https",
|
Scheme: "https",
|
||||||
Host: instance,
|
Host: instance,
|
||||||
Path: "api/v1/timelines/public",
|
|
||||||
RawQuery: params.Encode(),
|
RawQuery: params.Encode(),
|
||||||
}
|
}
|
||||||
|
withFiles := "false"
|
||||||
req, err := http.NewRequest(http.MethodGet, perform.String(), nil)
|
withReplies := "false"
|
||||||
if err != nil {
|
for _, instanceParam := range instanceParams {
|
||||||
return nil, err
|
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 = 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")
|
req.Header.Set("User-Agent", "Bloat")
|
||||||
resp, err := httpclient.Do(req)
|
client := http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
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("bad request", resp)
|
return nil, parseAPIError("Can't get remote timeline for " + instance + ", try select another type instance. Error" , 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
|
||||||
|
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", "<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)
|
||||||
|
}
|
||||||
|
default:
|
||||||
err = json.NewDecoder(resp.Body).Decode(&publicstatuses)
|
err = json.NewDecoder(resp.Body).Decode(&publicstatuses)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return publicstatuses, nil
|
return publicstatuses, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,7 @@ 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
|
||||||
|
|
|
@ -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) (err error) {
|
minID string, tag string, instance_type string) (err error) {
|
||||||
|
|
||||||
var nextLink, prevLink, title string
|
var nextLink, prevLink, title string
|
||||||
var statuses []*mastodon.Status
|
var statuses []*mastodon.Status
|
||||||
|
@ -159,13 +159,17 @@ 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 {
|
||||||
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
v := make(url.Values)
|
v := make(url.Values)
|
||||||
v.Set("max_id", statuses[len(statuses)-1].ID)
|
v.Set("max_id", statuses[len(statuses)-1].ID)
|
||||||
v.Set("instance", instance)
|
v.Set("instance", instance)
|
||||||
|
v.Set("instance_type", instance_type)
|
||||||
nextLink = "/timeline/" + tType + "?" + v.Encode()
|
nextLink = "/timeline/" + tType + "?" + v.Encode()
|
||||||
}
|
}
|
||||||
title = "True Remote Timeline"
|
title = "True Remote Timeline"
|
||||||
|
@ -223,6 +227,9 @@ 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)
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
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)
|
||||||
}
|
}
|
||||||
|
@ -252,6 +263,7 @@ 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,
|
||||||
|
|
|
@ -139,7 +139,8 @@ 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")
|
||||||
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)
|
}, SESSION, HTML)
|
||||||
|
|
||||||
defaultTimelinePage := handle(func(c *client) error {
|
defaultTimelinePage := handle(func(c *client) error {
|
||||||
|
|
|
@ -14,10 +14,23 @@
|
||||||
<button type="submit"> Submit </button>
|
<button type="submit"> Submit </button>
|
||||||
</form>
|
</form>
|
||||||
{{if eq .Instance ""}}
|
{{if eq .Instance ""}}
|
||||||
|
<a href="/timeline/tremote"> True remote timeline viewer </a>
|
||||||
{{else}}
|
{{else}}
|
||||||
<a href="/timeline/tremote?instance={{.Instance}}"> Look via True remote timeline viewer (works with mastodon API compatible instances) </a>
|
|
||||||
{{end}}
|
{{end}}
|
||||||
{{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>
|
||||||
|
</select>
|
||||||
|
</span>
|
||||||
|
<button type="submit"> Submit </button>
|
||||||
|
</form>
|
||||||
|
{{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">
|
||||||
<span class="post-form-field">
|
<span class="post-form-field">
|
||||||
|
|
Loading…
Reference in a new issue