mlmym/state.go

716 lines
18 KiB
Go
Raw Normal View History

2023-06-30 19:41:35 +00:00
package main
import (
"bytes"
"context"
_ "embed"
"encoding/json"
"errors"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/url"
2023-07-16 02:07:19 +00:00
"os"
"regexp"
2023-06-30 19:41:35 +00:00
"sort"
"strconv"
"strings"
"time"
"github.com/rystaf/go-lemmy"
"github.com/rystaf/go-lemmy/types"
)
type Comment struct {
P types.CommentView
C []Comment
Selected bool
State *State
Op string
ChildCount int
}
func (c *Comment) Submitter() bool {
return c.P.Comment.CreatorID == c.P.Post.CreatorID
}
2023-07-13 13:45:51 +00:00
func (c *Comment) ParentID() int {
path := strings.Split(c.P.Comment.Path, ".")
id, _ := strconv.Atoi(path[len(path)-2])
return id
}
2023-06-30 19:41:35 +00:00
type Person struct {
types.PersonViewSafe
}
type Activity struct {
Timestamp time.Time
Comment *Comment
Post *Post
Message *types.PrivateMessageView
}
type Post struct {
types.PostView
Rank int
State *State
}
type Session struct {
2023-07-24 02:12:01 +00:00
UserName string
UserID int
Communities []types.CommunityView
2023-06-30 19:41:35 +00:00
}
type State struct {
2023-07-26 20:53:06 +00:00
Watch bool
2023-07-23 22:19:53 +00:00
Version string
Client *lemmy.Client
HTTPClient *http.Client
Session *Session
Status int
Error error
Alert string
Host string
CommunityName string
Community *types.GetCommunityResponse
TopCommunities []types.CommunityView
Communities []types.CommunityView
UnreadCount int64
Sort string
CommentSort string
Listing string
Page int
Parts []string
Posts []Post
Comments []Comment
Activities []Activity
CommentCount int
PostID int
CommentID int
Context int
UserName string
User *types.GetPersonDetailsResponse
Now int64
XHR bool
Op string
Site *types.GetSiteResponse
Query string
SearchType string
Captcha *types.CaptchaResponse
Dark bool
ShowNSFW bool
HideInstanceNames bool
2023-06-30 19:41:35 +00:00
}
2023-07-16 02:07:19 +00:00
func (s State) Unknown() string {
fmt.Println(fmt.Sprintf("%v", s.Error))
re := regexp.MustCompile(`(.*?)@(.*?)@`)
if strings.Contains(fmt.Sprintf("%v", s.Error), "couldnt_find_community") {
matches := re.FindAllStringSubmatch(s.CommunityName+"@", -1)
if len(matches) < 1 || len(matches[0]) < 3 {
return ""
}
if matches[0][2] != s.Host {
remote := "/" + matches[0][2] + "/c/" + matches[0][1]
if os.Getenv("LEMMY_DOMAIN") != "" {
remote = "https:/" + remote
}
return remote
}
}
if strings.Contains(fmt.Sprintf("%v", s.Error), "couldnt_find_that_username_or_email") {
matches := re.FindAllStringSubmatch(s.UserName+"@", -1)
if len(matches) < 1 || len(matches[0]) < 3 {
return ""
}
if matches[0][2] != s.Host {
remote := "/" + matches[0][2] + "/u/" + matches[0][1]
if os.Getenv("LEMMY_DOMAIN") != "" {
remote = "https:/" + remote
}
return remote
}
}
return ""
}
2023-06-30 19:41:35 +00:00
func (p State) SortBy(v string) string {
var q string
if p.Query != "" || p.SearchType == "Communities" {
q = "q=" + p.Query + "&communityname=" + p.CommunityName + "&username=" + p.UserName + "&searchtype=" + p.SearchType + "&"
}
return "?" + q + "sort=" + v + "&listingType=" + p.Listing
}
func (p State) ListBy(v string) string {
var q string
if p.Query != "" || p.SearchType == "Communities" {
q = "q=" + p.Query + "&communityname=" + p.CommunityName + "&username=" + p.UserName + "&searchtype=" + p.SearchType + "&"
}
return "?" + q + "sort=" + p.Sort + "&listingType=" + v
}
func (p State) PrevPage() string {
var listing string
if p.Listing != "All" {
listing = "&listingType=" + p.Listing
}
var q string
if p.Query != "" || p.SearchType == "Communities" {
q = "q=" + p.Query + "&communityname=" + p.CommunityName + "&username=" + p.UserName + "&searchtype=" + p.SearchType + "&"
}
page := strconv.Itoa(p.Page - 1)
return "?" + q + "sort=" + p.Sort + listing + "&page=" + page
}
func (p State) NextPage() string {
var listing string
if p.Listing != "All" {
listing = "&listingType=" + p.Listing
}
var q string
if p.Query != "" || p.SearchType == "Communities" {
q = "q=" + p.Query + "&communityname=" + p.CommunityName + "&username=" + p.UserName + "&searchtype=" + p.SearchType + "&"
}
page := strconv.Itoa(p.Page + 1)
return "?" + q + "sort=" + p.Sort + listing + "&page=" + page
}
func (p State) Rank(v int) int {
return ((p.Page - 1) * 25) + v + 1
}
func (u *Person) FullUserName() string {
if u.Person.Local {
return u.Person.Name
}
l, err := url.Parse(u.Person.ActorID)
if err != nil {
fmt.Println(err)
return u.Person.Name
}
return u.Person.Name + "@" + l.Host
}
func (state *State) ParseQuery(RawQuery string) {
if RawQuery == "" {
return
}
m, _ := url.ParseQuery(RawQuery)
if len(m["listingType"]) > 0 {
state.Listing = m["listingType"][0]
}
if len(m["sort"]) > 0 {
state.Sort = m["sort"][0]
state.CommentSort = m["sort"][0]
2023-06-30 19:41:35 +00:00
}
if len(m["communityname"]) > 0 {
state.CommunityName = m["communityname"][0]
}
if len(m["username"]) > 0 {
state.UserName = m["username"][0]
}
if len(m["q"]) > 0 {
state.Query = m["q"][0]
}
if len(m["xhr"]) > 0 {
state.XHR = true
}
2023-07-05 14:20:07 +00:00
if len(m["view"]) > 0 {
if m["view"][0] == "Saved" {
state.Op = "Saved"
}
}
2023-06-30 19:41:35 +00:00
//if len(m["op"]) > 0 {
// state.Op = m["op"][0]
//}
if len(m["page"]) > 0 {
i, _ := strconv.Atoi(m["page"][0])
state.Page = i
}
}
2023-07-01 14:57:04 +00:00
func (state *State) LemmyError(domain string) error {
var nodeInfo NodeInfo
res, err := state.HTTPClient.Get("https://" + domain + "/nodeinfo/2.0.json")
if err != nil {
return err
}
if res.StatusCode != http.StatusOK {
return fmt.Errorf("Status Code: %v", res.StatusCode)
}
err = json.NewDecoder(res.Body).Decode(&nodeInfo)
if err != nil {
return err
}
if nodeInfo.Software.Name == "lemmy" {
return nil
}
return errors.New("Not a lemmy instance")
}
2023-06-30 19:41:35 +00:00
func (state *State) GetCaptcha() {
resp, err := state.Client.Captcha(context.Background(), types.GetCaptcha{})
if err != nil {
fmt.Printf("Get %v %v", err, resp)
} else {
captcha, _ := resp.Ok.Value()
if resp.Ok.IsValid() {
state.Captcha = &captcha
}
}
}
func (state *State) GetSite() {
resp, err := state.Client.Site(context.Background(), types.GetSite{})
if err != nil {
2023-07-20 23:35:42 +00:00
fmt.Println(err)
2023-06-30 19:41:35 +00:00
state.Status = http.StatusInternalServerError
2023-07-11 14:21:57 +00:00
state.Host = "."
2023-07-20 23:35:42 +00:00
state.Error = errors.New("unable to retrieve site")
2023-06-30 19:41:35 +00:00
return
}
state.Site = resp
2023-07-24 02:12:01 +00:00
if !state.Site.MyUser.IsValid() {
return
}
for _, c := range state.Site.MyUser.MustValue().Follows {
state.Session.Communities = append(state.Session.Communities, types.CommunityView{
Community: c.Community,
Subscribed: "Subscribed",
})
}
sort.Slice(state.Session.Communities, func(a, b int) bool {
return state.Session.Communities[a].Community.Name < state.Session.Communities[b].Community.Name
})
2023-06-30 19:41:35 +00:00
}
func (state *State) GetComment(commentid int) {
if state.Sort != "Hot" && state.Sort != "Top" && state.Sort != "Old" && state.Sort != "New" {
state.Sort = "Hot"
}
2023-06-30 19:41:35 +00:00
state.CommentID = commentid
cresp, err := state.Client.Comments(context.Background(), types.GetComments{
ParentID: types.NewOptional(state.CommentID),
Sort: types.NewOptional(types.CommentSortType(state.CommentSort)),
2023-06-30 19:41:35 +00:00
Type: types.NewOptional(types.ListingType("All")),
2023-07-05 22:34:46 +00:00
Limit: types.NewOptional(int64(50)),
2023-06-30 19:41:35 +00:00
})
if err != nil {
fmt.Println(err)
state.Status = http.StatusInternalServerError
return
}
state.CommentCount = len(cresp.Comments)
for _, c := range cresp.Comments {
if c.Comment.ID == state.CommentID {
state.PostID = c.Comment.PostID
//if state.Session != nil && state.Session.UserID
comment := Comment{
P: c,
Selected: !state.XHR,
State: state,
Op: state.Op,
}
getChildren(&comment, cresp.Comments, c.Post.CreatorID)
state.Comments = append(state.Comments, comment)
}
}
2023-07-13 13:45:51 +00:00
ctx, err := state.GetContext(state.Context, state.Comments[0])
if err != nil {
fmt.Println(err)
} else {
state.Comments = []Comment{ctx}
}
}
func (state *State) GetContext(depth int, comment Comment) (ctx Comment, err error) {
if depth < 1 || comment.ParentID() == 0 {
return comment, nil
}
cresp, err := state.Client.Comment(context.Background(), types.GetComment{
ID: comment.ParentID(),
})
if err != nil {
return
}
ctx, err = state.GetContext(depth-1, Comment{
P: cresp.CommentView,
State: state,
C: []Comment{comment},
ChildCount: comment.ChildCount + 1,
})
return
2023-06-30 19:41:35 +00:00
}
func (state *State) GetComments() {
if state.Sort != "Hot" && state.Sort != "Top" && state.Sort != "Old" && state.Sort != "New" {
state.Sort = "Hot"
}
2023-06-30 19:41:35 +00:00
cresp, err := state.Client.Comments(context.Background(), types.GetComments{
PostID: types.NewOptional(state.PostID),
Sort: types.NewOptional(types.CommentSortType(state.CommentSort)),
2023-06-30 19:41:35 +00:00
Type: types.NewOptional(types.ListingType("All")),
2023-07-05 22:34:46 +00:00
Limit: types.NewOptional(int64(50)),
2023-06-30 19:41:35 +00:00
Page: types.NewOptional(int64(state.Page)),
})
if err != nil {
state.Status = http.StatusInternalServerError
fmt.Println(err)
2023-06-30 19:41:35 +00:00
return
}
state.CommentCount = len(cresp.Comments)
for _, c := range cresp.Comments {
levels := strings.Split(c.Comment.Path, ".")
if len(levels) != 2 {
continue
}
comment := Comment{P: c, State: state}
var postCreatorID int
if len(state.Posts) > 0 {
postCreatorID = state.Posts[0].Post.CreatorID
}
getChildren(&comment, cresp.Comments, postCreatorID)
state.Comments = append(state.Comments, comment)
}
}
func (state *State) GetMessages() {
if resp, err := state.Client.PrivateMessages(context.Background(), types.GetPrivateMessages{
Page: types.NewOptional(int64(state.Page)),
}); err != nil {
fmt.Println(err)
state.Status = http.StatusInternalServerError
return
} else {
for _, m := range resp.PrivateMessages {
message := m
state.Activities = append(state.Activities, Activity{
Timestamp: m.PrivateMessage.Published.Time,
Message: &message,
})
}
}
if resp, err := state.Client.PersonMentions(context.Background(), types.GetPersonMentions{
Page: types.NewOptional(int64(state.Page)),
}); err != nil {
fmt.Println(err)
state.Status = http.StatusInternalServerError
return
} else {
for _, m := range resp.Mentions {
var unread string
if !m.PersonMention.Read {
unread = "unread"
}
comment := Comment{
P: types.CommentView{
Comment: m.Comment,
},
Op: unread,
State: state,
}
state.Activities = append(state.Activities, Activity{
Timestamp: m.Comment.Published.Time,
Comment: &comment,
})
}
}
if resp, err := state.Client.Replies(context.Background(), types.GetReplies{
Page: types.NewOptional(int64(state.Page)),
}); err != nil {
fmt.Println(err)
state.Status = http.StatusInternalServerError
return
} else {
for _, m := range resp.Replies {
var unread string
if !m.CommentReply.Read {
unread = "unread"
}
comment := Comment{
P: types.CommentView{
Comment: m.Comment,
Post: m.Post,
Creator: m.Creator,
Community: m.Community,
Counts: m.Counts,
2023-06-30 19:41:35 +00:00
},
Op: unread,
State: state,
}
state.Activities = append(state.Activities, Activity{
Timestamp: m.Comment.Published.Time,
Comment: &comment,
})
}
}
}
func (state *State) GetUser(username string) {
state.UserName = username
limit := 12
if state.Op == "send_message" {
limit = 1
}
resp, err := state.Client.PersonDetails(context.Background(), types.GetPersonDetails{
2023-07-05 14:20:07 +00:00
Username: types.NewOptional(state.UserName),
Page: types.NewOptional(int64(state.Page)),
Limit: types.NewOptional(int64(limit)),
SavedOnly: types.NewOptional(state.Op == "Saved"),
2023-06-30 19:41:35 +00:00
})
if err != nil {
fmt.Println(err)
2023-07-16 02:07:19 +00:00
state.Error = err
2023-06-30 19:41:35 +00:00
state.Status = http.StatusInternalServerError
return
}
state.User = resp
if state.Query != "" {
return
}
for i, p := range resp.Posts {
post := Post{
PostView: resp.Posts[i],
Rank: -1,
State: state,
}
state.Activities = append(state.Activities, Activity{
Timestamp: p.Post.Published.Time,
Post: &post,
})
}
for _, c := range resp.Comments {
comment := Comment{P: c, State: state}
state.Activities = append(state.Activities, Activity{
Timestamp: c.Comment.Published.Time,
Comment: &comment,
})
}
sort.Slice(state.Activities, func(i, j int) bool {
return state.Activities[i].Timestamp.After(state.Activities[j].Timestamp)
})
}
func (state *State) GetUnreadCount() {
resp, err := state.Client.UnreadCount(context.Background(), types.GetUnreadCount{})
if err != nil {
fmt.Println(err)
return
}
state.UnreadCount = resp.PrivateMessages + resp.Mentions + resp.Replies
}
func (state *State) GetCommunities() {
resp, err := state.Client.Communities(context.Background(), types.ListCommunities{
Sort: types.NewOptional(types.SortType("TopAll")),
Limit: types.NewOptional(int64(20)),
})
if err != nil {
return
}
state.TopCommunities = resp.Communities
}
func (state *State) MarkAllAsRead() {
_, err := state.Client.MarkAllAsRead(context.Background(), types.MarkAllAsRead{})
if err != nil {
fmt.Println(err)
return
}
}
func (state *State) GetPosts() {
posts := types.GetPosts{
Sort: types.NewOptional(types.SortType(state.Sort)),
Type: types.NewOptional(types.ListingType(state.Listing)),
Limit: types.NewOptional(int64(25)),
Page: types.NewOptional(int64(state.Page)),
}
if state.CommunityName != "" {
posts.CommunityName = types.NewOptional(state.CommunityName)
}
resp, err := state.Client.Posts(context.Background(), posts)
2023-06-30 19:41:35 +00:00
if err != nil {
fmt.Println(err)
state.Status = http.StatusInternalServerError
return
} else {
for i, p := range resp.Posts {
state.Posts = append(state.Posts, Post{
PostView: p,
Rank: (state.Page-1)*25 + i + 1,
State: state,
})
}
}
}
func (state *State) Search(searchtype string) {
if state.Query == "" && searchtype == "Communities" {
if state.Listing == "Subscribed" {
if state.Page > 1 {
return
}
2023-07-24 02:12:01 +00:00
if state.Site == nil {
state.GetSite()
}
2023-07-24 02:12:01 +00:00
state.Communities = state.Session.Communities
return
}
2023-06-30 19:41:35 +00:00
resp, err := state.Client.Communities(context.Background(), types.ListCommunities{
Type: types.NewOptional(types.ListingType(state.Listing)),
2023-06-30 19:41:35 +00:00
Sort: types.NewOptional(types.SortType(state.Sort)),
Limit: types.NewOptional(int64(25)),
Page: types.NewOptional(int64(state.Page)),
})
if err != nil {
fmt.Println(err)
return
}
state.Communities = resp.Communities
return
}
search := types.Search{
Q: state.Query,
Sort: types.NewOptional(types.SortType(state.Sort)),
2023-07-16 17:45:40 +00:00
ListingType: types.NewOptional(types.ListingType(state.Listing)),
2023-06-30 19:41:35 +00:00
Type: types.NewOptional(types.SearchType(searchtype)),
Limit: types.NewOptional(int64(25)),
Page: types.NewOptional(int64(state.Page)),
}
if state.CommunityName != "" {
search.CommunityName = types.NewOptional(state.CommunityName)
}
if state.User != nil {
search.CreatorID = types.NewOptional(state.User.PersonView.Person.ID)
}
resp, err := state.Client.Search(context.Background(), search)
if err != nil {
fmt.Println(err)
state.Status = http.StatusInternalServerError
return
} else {
for i, p := range resp.Posts {
state.Posts = append(state.Posts, Post{
PostView: p,
Rank: (state.Page-1)*25 + i + 1,
State: state,
})
}
for _, c := range resp.Comments {
2023-07-13 13:45:51 +00:00
comment := Comment{
2023-06-30 19:41:35 +00:00
P: c,
State: state,
2023-07-13 13:45:51 +00:00
}
state.Activities = append(state.Activities, Activity{
Timestamp: c.Comment.Published.Time,
Comment: &comment,
2023-06-30 19:41:35 +00:00
})
}
state.Communities = resp.Communities
}
}
func (state *State) GetPost(postid int) {
if postid == 0 {
return
}
state.PostID = postid
// get post
resp, err := state.Client.Post(context.Background(), types.GetPost{
ID: types.NewOptional(state.PostID),
})
if err != nil {
state.Status = http.StatusInternalServerError
2023-07-16 13:00:49 +00:00
state.Error = err
2023-06-30 19:41:35 +00:00
return
}
2023-07-16 13:00:49 +00:00
state.Posts = []Post{Post{
PostView: resp.PostView,
State: state,
}}
if state.CommentID > 0 && len(state.Posts) > 0 {
state.Posts[0].Rank = -1
}
state.CommunityName = resp.PostView.Community.Name
cresp := types.GetCommunityResponse{
CommunityView: resp.CommunityView,
Moderators: resp.Moderators,
}
state.Community = &cresp
2023-06-30 19:41:35 +00:00
}
func (state *State) GetCommunity(communityName string) {
if communityName != "" {
state.CommunityName = communityName
}
if state.CommunityName == "" {
return
}
resp, err := state.Client.Community(context.Background(), types.GetCommunity{
Name: types.NewOptional(state.CommunityName),
})
if err != nil {
state.Error = err
} else {
state.Community = resp
}
}
func (state *State) UploadImage(file multipart.File, header *multipart.FileHeader) (*PictrsResponse, error) {
defer file.Close()
body := new(bytes.Buffer)
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("images[]", header.Filename)
if err != nil {
return nil, err
}
io.Copy(part, file)
writer.Close()
req, err := http.NewRequest("POST", "https://"+state.Host+"/pictrs/image", body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", writer.FormDataContentType())
req.Header.Set("Cookie", "jwt="+state.Client.Token)
res, err := state.HTTPClient.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
var pres PictrsResponse
if err := json.NewDecoder(res.Body).Decode(&pres); err != nil {
return nil, err
}
if pres.Message != "ok" {
return &pres, errors.New(pres.Message)
}
return &pres, nil
}
func getChildren(parent *Comment, pool []types.CommentView, postCreatorID int) {
var children []Comment
total := int32(0)
2023-06-30 19:41:35 +00:00
for _, c := range pool {
levels := strings.Split(c.Comment.Path, ".")
for i, l := range levels {
id, _ := strconv.Atoi(l)
if id == parent.P.Comment.ID {
if i == (len(levels) - 2) {
children = append(children, Comment{
P: c,
C: children,
State: parent.State,
})
total += c.Counts.ChildCount
2023-06-30 19:41:35 +00:00
}
}
}
}
for i, _ := range children {
getChildren(&children[i], pool, postCreatorID)
parent.ChildCount += 1
2023-06-30 19:41:35 +00:00
}
parent.C = children
parent.P.Counts.ChildCount -= total
2023-06-30 19:41:35 +00:00
}