175 lines
4.6 KiB
Go
175 lines
4.6 KiB
Go
package services
|
|
|
|
import (
|
|
"CatsOfMastodonBotGo/internal/config"
|
|
"CatsOfMastodonBotGo/internal/database"
|
|
"CatsOfMastodonBotGo/internal/domain"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/rand"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
type PostService struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
var PostServiceInstance *PostService
|
|
|
|
// Constructor
|
|
func InitPostService() {
|
|
PostServiceInstance = &PostService{db: database.Gorm}
|
|
}
|
|
|
|
func (*PostService) GetPostsFromApi(ctx context.Context, tag string, instance string) (error, []domain.Post) {
|
|
var requestUrl = instance + "/api/v1/timelines/tag/" + tag + "?limit=40"
|
|
req, err := http.NewRequestWithContext(ctx, "GET", requestUrl, nil)
|
|
if err != nil {
|
|
return err, nil
|
|
}
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return err, nil
|
|
}
|
|
if resp.StatusCode != 200 || strings.Split(strings.ToLower(resp.Header.Get("Content-Type")), ";")[0] != "application/json" {
|
|
return fmt.Errorf("Status code:", resp.StatusCode, " Content-Type:", resp.Header.Get("Content-Type")), nil
|
|
}
|
|
|
|
var posts []domain.Post = nil
|
|
err = json.NewDecoder(resp.Body).Decode(&posts)
|
|
if err != nil {
|
|
return err, nil
|
|
}
|
|
// defer: it basically means "do this later when the function returns"
|
|
defer resp.Body.Close()
|
|
if posts == nil {
|
|
return fmt.Errorf("no posts found for tag %s on instance %s", tag, instance), nil
|
|
}
|
|
return nil, posts
|
|
}
|
|
|
|
func (ps *PostService) GetExistingPostIds() []string {
|
|
var existingPostIds []string
|
|
ps.db.Model(&domain.Post{}).Pluck("id", &existingPostIds)
|
|
return existingPostIds
|
|
}
|
|
|
|
func (ps *PostService) GetExistingAccountIds() []string {
|
|
var existingAccountIds []string
|
|
ps.db.Model(&domain.Account{}).Pluck("acc_id", &existingAccountIds)
|
|
return existingAccountIds
|
|
}
|
|
|
|
func (*PostService) GetNewPosts(existingPostIds []string, posts []domain.Post) []domain.Post {
|
|
var newPosts []domain.Post = nil
|
|
for _, post := range posts {
|
|
if !arrayContains(existingPostIds, post.ID) && len(post.Attachments) > 0 && !post.Account.IsBot {
|
|
var allImageMedia = true
|
|
for _, attachment := range post.Attachments {
|
|
if attachment.Type != "image" {
|
|
allImageMedia = false
|
|
break
|
|
}
|
|
} // Inefficient but anyways
|
|
if allImageMedia {
|
|
newPosts = append(newPosts, post)
|
|
}
|
|
}
|
|
}
|
|
return newPosts
|
|
}
|
|
|
|
func (*PostService) GetNewAccounts(existingAccountIds []string, posts []domain.Post) []domain.Account {
|
|
var newAccounts []domain.Account = nil
|
|
for _, post := range posts {
|
|
if !arrayContains(existingAccountIds, post.Account.AccId) {
|
|
newAccounts = append(newAccounts, post.Account)
|
|
}
|
|
}
|
|
return newAccounts
|
|
}
|
|
|
|
func (ps *PostService) InsertNewPosts(newPosts []domain.Post) int {
|
|
return int(ps.db.Create(&newPosts).RowsAffected)
|
|
}
|
|
|
|
func (ps *PostService) InsertNewAccounts(newAccounts []domain.Account) int {
|
|
return int(ps.db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&newAccounts).RowsAffected)
|
|
}
|
|
|
|
// From this point on, its for the api endpoints
|
|
|
|
func (ps *PostService) GetRandomPost() domain.Post {
|
|
var post domain.Post
|
|
var postIDs []uint
|
|
|
|
// AI Enhanced
|
|
// Step 1: Fetch eligible post IDs (with at least one approved attachment)
|
|
ps.db.
|
|
Model(&domain.Post{}).
|
|
Where("exists (select 1 from media_attachments where post_id = posts.id and approved = ?)", true).
|
|
Pluck("id", &postIDs)
|
|
|
|
if len(postIDs) == 0 {
|
|
return domain.Post{} // No eligible posts
|
|
}
|
|
|
|
// Step 2: Pick a random ID in Go
|
|
randomID := postIDs[rand.Intn(len(postIDs))]
|
|
|
|
// Step 3: Load the full post with related data
|
|
ps.db.
|
|
Preload("Account").
|
|
Preload("Attachments", "Approved = ?", true).
|
|
First(&post, randomID)
|
|
|
|
// Step 4: Keep only the first attachment if any
|
|
if len(post.Attachments) > 0 {
|
|
post.Attachments = []domain.MediaAttachment{post.Attachments[0]}
|
|
}
|
|
|
|
return post
|
|
}
|
|
|
|
func (ps *PostService) ApproveMedia(mediaId string) bool {
|
|
return ps.db.Model(&domain.MediaAttachment{}).
|
|
Where("id = ?", mediaId).
|
|
Update("approved", true).RowsAffected > 0
|
|
}
|
|
|
|
func (ps *PostService) RejectMedia(mediaId string) bool {
|
|
return ps.db.Model(&domain.MediaAttachment{}).
|
|
Where("id = ?", mediaId).
|
|
Update("rejected", true).RowsAffected > 0
|
|
}
|
|
|
|
// Get a post which approve and rejet are false (For admin panel)
|
|
func (ps *PostService) GetMedia() domain.MediaAttachment {
|
|
var media domain.MediaAttachment
|
|
orderExpr := "RANDOM()" // sqlite
|
|
if config.Config.DBEngine != "sqlite" {
|
|
orderExpr = "RAND()" // mariadb/mysql
|
|
}
|
|
ps.db.Model(&domain.MediaAttachment{}).
|
|
Where("approved = ?", false).
|
|
Where("rejected = ?", false).
|
|
Order(orderExpr).
|
|
First(&media)
|
|
return media
|
|
}
|
|
|
|
func arrayContains(arr []string, str string) bool {
|
|
for _, a := range arr {
|
|
if a == str {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|