616 lines
17 KiB
Go
616 lines
17 KiB
Go
package executor
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
|
|
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
|
|
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
|
|
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
|
|
"codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
|
|
)
|
|
|
|
type ShowExecutor struct {
|
|
*flag.FlagSet
|
|
|
|
printer *printer.Printer
|
|
config *config.Config
|
|
myAccount bool
|
|
excludeBoosts bool
|
|
excludeReplies bool
|
|
onlyMedia bool
|
|
onlyPinned bool
|
|
onlyPublic bool
|
|
showInBrowser bool
|
|
showUserPreferences bool
|
|
showStatuses bool
|
|
skipAccountRelationship bool
|
|
resourceType string
|
|
accountName string
|
|
statusID string
|
|
timelineCategory string
|
|
listID string
|
|
tag string
|
|
pollID string
|
|
fromResourceType string
|
|
limit int
|
|
attachmentIDs MultiStringFlagValue
|
|
}
|
|
|
|
func NewShowExecutor(printer *printer.Printer, config *config.Config, name, summary string) *ShowExecutor {
|
|
showExe := ShowExecutor{
|
|
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
|
|
|
|
printer: printer,
|
|
config: config,
|
|
}
|
|
|
|
showExe.BoolVar(&showExe.myAccount, flagMyAccount, false, "Set to true to lookup your account")
|
|
showExe.BoolVar(&showExe.skipAccountRelationship, flagSkipRelationship, false, "Set to true to skip showing your relationship to the specified account")
|
|
showExe.BoolVar(&showExe.showUserPreferences, flagShowPreferences, false, "Show your preferences")
|
|
showExe.BoolVar(&showExe.showInBrowser, flagBrowser, false, "Set to true to view in the browser")
|
|
showExe.BoolVar(&showExe.showStatuses, flagShowStatuses, false, "Set to true to view the statuses created from the specified account")
|
|
showExe.BoolVar(&showExe.excludeReplies, flagExcludeReplies, false, "Set to true to exclude statuses that are a reply to another status")
|
|
showExe.BoolVar(&showExe.excludeBoosts, flagExcludeBoosts, false, "Set to true to exclude statuses that are boosts of another status")
|
|
showExe.BoolVar(&showExe.onlyPinned, flagOnlyPinned, false, "Set to true to show only the account's pinned statuses")
|
|
showExe.BoolVar(&showExe.onlyMedia, flagOnlyMedia, false, "Set to true to show only the statuses with media attachments")
|
|
showExe.BoolVar(&showExe.onlyPublic, flagOnlyPublic, false, "Set to true to show only the account's public posts")
|
|
showExe.StringVar(&showExe.resourceType, flagType, "", "Specify the type of resource to display")
|
|
showExe.StringVar(&showExe.accountName, flagAccountName, "", "Specify the account name in full (username@domain)")
|
|
showExe.StringVar(&showExe.statusID, flagStatusID, "", "Specify the ID of the status to display")
|
|
showExe.StringVar(&showExe.timelineCategory, flagTimelineCategory, model.TimelineCategoryHome, "Specify the timeline category to view")
|
|
showExe.StringVar(&showExe.listID, flagListID, "", "Specify the ID of the list to display")
|
|
showExe.StringVar(&showExe.tag, flagTag, "", "Specify the name of the tag to use")
|
|
showExe.StringVar(&showExe.pollID, flagPollID, "", "Specify the ID of the poll to display")
|
|
showExe.Var(&showExe.attachmentIDs, flagAttachmentID, "Specify the ID of the media attachment to display")
|
|
showExe.StringVar(&showExe.fromResourceType, flagFrom, "", "Specify the resource type to view the target resource from (e.g. status for viewing media from, etc)")
|
|
showExe.IntVar(&showExe.limit, flagLimit, 20, "Specify the limit of items to display")
|
|
|
|
showExe.Usage = commandUsageFunc(name, summary, showExe.FlagSet)
|
|
|
|
return &showExe
|
|
}
|
|
|
|
func (s *ShowExecutor) Execute() error {
|
|
if s.resourceType == "" {
|
|
return FlagNotSetError{flagText: flagType}
|
|
}
|
|
|
|
funcMap := map[string]func(*client.Client) error{
|
|
resourceInstance: s.showInstance,
|
|
resourceAccount: s.showAccount,
|
|
resourceStatus: s.showStatus,
|
|
resourceTimeline: s.showTimeline,
|
|
resourceList: s.showList,
|
|
resourceFollowers: s.showFollowers,
|
|
resourceFollowing: s.showFollowing,
|
|
resourceBlocked: s.showBlocked,
|
|
resourceBookmarks: s.showBookmarks,
|
|
resourceLiked: s.showLiked,
|
|
resourceStarred: s.showLiked,
|
|
resourceFollowRequest: s.showFollowRequests,
|
|
resourcePoll: s.showPoll,
|
|
resourceMutedAccounts: s.showMutedAccounts,
|
|
resourceMedia: s.showMedia,
|
|
resourceMediaAttachment: s.showMediaAttachment,
|
|
}
|
|
|
|
doFunc, ok := funcMap[s.resourceType]
|
|
if !ok {
|
|
return UnsupportedTypeError{resourceType: s.resourceType}
|
|
}
|
|
|
|
gtsClient, err := client.NewClientFromFile(s.config.CredentialsFile)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to create the GoToSocial client: %w", err)
|
|
}
|
|
|
|
return doFunc(gtsClient)
|
|
}
|
|
|
|
func (s *ShowExecutor) showInstance(gtsClient *client.Client) error {
|
|
instance, err := gtsClient.GetInstance()
|
|
if err != nil {
|
|
return fmt.Errorf("unable to retrieve the instance details: %w", err)
|
|
}
|
|
|
|
s.printer.PrintInstance(instance)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ShowExecutor) showAccount(gtsClient *client.Client) error {
|
|
var (
|
|
account model.Account
|
|
err error
|
|
)
|
|
|
|
if s.myAccount {
|
|
account, err = getMyAccount(gtsClient, s.config.CredentialsFile)
|
|
if err != nil {
|
|
return fmt.Errorf("received an error while getting the account details: %w", err)
|
|
}
|
|
} else {
|
|
if s.accountName == "" {
|
|
return FlagNotSetError{flagText: flagAccountName}
|
|
}
|
|
|
|
account, err = getAccount(gtsClient, s.accountName)
|
|
if err != nil {
|
|
return fmt.Errorf("received an error while getting the account details: %w", err)
|
|
}
|
|
}
|
|
|
|
if s.showInBrowser {
|
|
if err := utilities.OpenLink(s.config.Integrations.Browser, account.URL); err != nil {
|
|
return fmt.Errorf("unable to open link: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
var (
|
|
relationship *model.AccountRelationship
|
|
preferences *model.Preferences
|
|
statuses *model.StatusList
|
|
)
|
|
|
|
if !s.myAccount && !s.skipAccountRelationship {
|
|
relationship, err = gtsClient.GetAccountRelationship(account.ID)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to retrieve the relationship to this account: %w", err)
|
|
}
|
|
}
|
|
|
|
if s.myAccount && s.showUserPreferences {
|
|
preferences, err = gtsClient.GetUserPreferences()
|
|
if err != nil {
|
|
return fmt.Errorf("unable to retrieve the user preferences: %w", err)
|
|
}
|
|
}
|
|
|
|
if s.showStatuses {
|
|
form := client.GetAccountStatusesForm{
|
|
AccountID: account.ID,
|
|
Limit: s.limit,
|
|
ExcludeReplies: s.excludeReplies,
|
|
ExcludeReblogs: s.excludeBoosts,
|
|
Pinned: s.onlyPinned,
|
|
OnlyMedia: s.onlyMedia,
|
|
OnlyPublic: s.onlyPublic,
|
|
}
|
|
|
|
statuses, err = gtsClient.GetAccountStatuses(form)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to retrieve the account's statuses: %w", err)
|
|
}
|
|
}
|
|
|
|
s.printer.PrintAccount(account, relationship, preferences, statuses)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ShowExecutor) showStatus(gtsClient *client.Client) error {
|
|
if s.statusID == "" {
|
|
return FlagNotSetError{flagText: flagStatusID}
|
|
}
|
|
|
|
status, err := gtsClient.GetStatus(s.statusID)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to retrieve the status: %w", err)
|
|
}
|
|
|
|
if s.showInBrowser {
|
|
if err := utilities.OpenLink(s.config.Integrations.Browser, status.URL); err != nil {
|
|
return fmt.Errorf("unable to open link: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
s.printer.PrintStatus(status)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ShowExecutor) showTimeline(gtsClient *client.Client) error {
|
|
var (
|
|
timeline model.StatusList
|
|
err error
|
|
)
|
|
|
|
switch s.timelineCategory {
|
|
case model.TimelineCategoryHome:
|
|
timeline, err = gtsClient.GetHomeTimeline(s.limit)
|
|
case model.TimelineCategoryPublic:
|
|
timeline, err = gtsClient.GetPublicTimeline(s.limit)
|
|
case model.TimelineCategoryList:
|
|
if s.listID == "" {
|
|
return FlagNotSetError{flagText: flagListID}
|
|
}
|
|
|
|
var list model.List
|
|
|
|
list, err = gtsClient.GetList(s.listID)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to retrieve the list: %w", err)
|
|
}
|
|
|
|
timeline, err = gtsClient.GetListTimeline(list.ID, list.Title, s.limit)
|
|
case model.TimelineCategoryTag:
|
|
if s.tag == "" {
|
|
return FlagNotSetError{flagText: flagTag}
|
|
}
|
|
|
|
timeline, err = gtsClient.GetTagTimeline(s.tag, s.limit)
|
|
default:
|
|
return model.InvalidTimelineCategoryError{Value: s.timelineCategory}
|
|
}
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("unable to retrieve the %s timeline: %w", s.timelineCategory, err)
|
|
}
|
|
|
|
if len(timeline.Statuses) == 0 {
|
|
s.printer.PrintInfo("There are no statuses in this timeline.\n")
|
|
|
|
return nil
|
|
}
|
|
|
|
s.printer.PrintStatusList(timeline)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ShowExecutor) showList(gtsClient *client.Client) error {
|
|
if s.listID == "" {
|
|
return s.showLists(gtsClient)
|
|
}
|
|
|
|
list, err := gtsClient.GetList(s.listID)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to retrieve the list: %w", err)
|
|
}
|
|
|
|
accounts, err := gtsClient.GetAccountsFromList(s.listID, 0)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to retrieve the accounts from the list: %w", err)
|
|
}
|
|
|
|
if len(accounts) > 0 {
|
|
accountMap := make(map[string]string)
|
|
for i := range accounts {
|
|
accountMap[accounts[i].Acct] = accounts[i].Username
|
|
}
|
|
|
|
list.Accounts = accountMap
|
|
}
|
|
|
|
s.printer.PrintList(list)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ShowExecutor) showLists(gtsClient *client.Client) error {
|
|
lists, err := gtsClient.GetAllLists()
|
|
if err != nil {
|
|
return fmt.Errorf("unable to retrieve the lists: %w", err)
|
|
}
|
|
|
|
if len(lists) == 0 {
|
|
s.printer.PrintInfo("You have no lists.\n")
|
|
|
|
return nil
|
|
}
|
|
|
|
s.printer.PrintLists(lists)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ShowExecutor) showFollowers(gtsClient *client.Client) error {
|
|
if s.fromResourceType == "" {
|
|
return FlagNotSetError{flagText: flagFrom}
|
|
}
|
|
|
|
funcMap := map[string]func(*client.Client) error{
|
|
resourceAccount: s.showFollowersFromAccount,
|
|
}
|
|
|
|
doFunc, ok := funcMap[s.fromResourceType]
|
|
if !ok {
|
|
return UnsupportedShowOperationError{
|
|
ResourceType: s.resourceType,
|
|
ShowFromResourceType: s.fromResourceType,
|
|
}
|
|
}
|
|
|
|
return doFunc(gtsClient)
|
|
}
|
|
|
|
func (s *ShowExecutor) showFollowersFromAccount(gtsClient *client.Client) error {
|
|
accountID, err := getAccountID(gtsClient, s.myAccount, s.accountName, s.config.CredentialsFile)
|
|
if err != nil {
|
|
return fmt.Errorf("received an error while getting the account ID: %w", err)
|
|
}
|
|
|
|
followers, err := gtsClient.GetFollowers(accountID, s.limit)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to retrieve the list of followers: %w", err)
|
|
}
|
|
|
|
if len(followers.Accounts) > 0 {
|
|
s.printer.PrintAccountList(followers)
|
|
} else {
|
|
s.printer.PrintInfo("There are no followers for this account (or the list is hidden).\n")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ShowExecutor) showFollowing(gtsClient *client.Client) error {
|
|
if s.fromResourceType == "" {
|
|
return FlagNotSetError{flagText: flagFrom}
|
|
}
|
|
|
|
funcMap := map[string]func(*client.Client) error{
|
|
resourceAccount: s.showFollowingFromAccount,
|
|
}
|
|
|
|
doFunc, ok := funcMap[s.fromResourceType]
|
|
if !ok {
|
|
return UnsupportedShowOperationError{
|
|
ResourceType: s.resourceType,
|
|
ShowFromResourceType: s.fromResourceType,
|
|
}
|
|
}
|
|
|
|
return doFunc(gtsClient)
|
|
}
|
|
|
|
func (s *ShowExecutor) showFollowingFromAccount(gtsClient *client.Client) error {
|
|
accountID, err := getAccountID(gtsClient, s.myAccount, s.accountName, s.config.CredentialsFile)
|
|
if err != nil {
|
|
return fmt.Errorf("received an error while getting the account ID: %w", err)
|
|
}
|
|
|
|
following, err := gtsClient.GetFollowing(accountID, s.limit)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to retrieve the list of followed accounts: %w", err)
|
|
}
|
|
|
|
if len(following.Accounts) > 0 {
|
|
s.printer.PrintAccountList(following)
|
|
} else {
|
|
s.printer.PrintInfo("This account is not following anyone or the list is hidden.\n")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ShowExecutor) showBlocked(gtsClient *client.Client) error {
|
|
blocked, err := gtsClient.GetBlockedAccounts(s.limit)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to retrieve the list of blocked accounts: %w", err)
|
|
}
|
|
|
|
if len(blocked.Accounts) > 0 {
|
|
s.printer.PrintAccountList(blocked)
|
|
} else {
|
|
s.printer.PrintInfo("You have no blocked accounts.\n")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ShowExecutor) showBookmarks(gtsClient *client.Client) error {
|
|
bookmarks, err := gtsClient.GetBookmarks(s.limit)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to retrieve the list of bookmarks: %w", err)
|
|
}
|
|
|
|
if len(bookmarks.Statuses) > 0 {
|
|
s.printer.PrintStatusList(bookmarks)
|
|
} else {
|
|
s.printer.PrintInfo("You have no bookmarks.\n")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ShowExecutor) showLiked(gtsClient *client.Client) error {
|
|
liked, err := gtsClient.GetLikedStatuses(s.limit, s.resourceType)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to retrieve the list of your %s statuses: %w", s.resourceType, err)
|
|
}
|
|
|
|
if len(liked.Statuses) > 0 {
|
|
s.printer.PrintStatusList(liked)
|
|
} else {
|
|
s.printer.PrintInfo("You have no " + s.resourceType + " statuses.\n")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ShowExecutor) showFollowRequests(gtsClient *client.Client) error {
|
|
accounts, err := gtsClient.GetFollowRequests(s.limit)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to retrieve the list of follow requests: %w", err)
|
|
}
|
|
|
|
if len(accounts.Accounts) > 0 {
|
|
s.printer.PrintAccountList(accounts)
|
|
} else {
|
|
s.printer.PrintInfo("You have no follow requests.\n")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ShowExecutor) showPoll(gtsClient *client.Client) error {
|
|
if s.pollID == "" {
|
|
return FlagNotSetError{flagText: flagPollID}
|
|
}
|
|
|
|
poll, err := gtsClient.GetPoll(s.pollID)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to retrieve the poll: %w", err)
|
|
}
|
|
|
|
s.printer.PrintPoll(poll)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ShowExecutor) showMutedAccounts(gtsClient *client.Client) error {
|
|
muted, err := gtsClient.GetMutedAccounts(s.limit)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to retrieve the list of muted accounts: %w", err)
|
|
}
|
|
|
|
if len(muted.Accounts) > 0 {
|
|
s.printer.PrintAccountList(muted)
|
|
} else {
|
|
s.printer.PrintInfo("You have not muted any accounts.\n")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ShowExecutor) showMediaAttachment(gtsClient *client.Client) error {
|
|
if len(s.attachmentIDs) == 0 {
|
|
return FlagNotSetError{flagText: flagAttachmentID}
|
|
}
|
|
|
|
if len(s.attachmentIDs) != 1 {
|
|
return fmt.Errorf(
|
|
"unexpected number of attachment IDs received: want 1, got %d",
|
|
len(s.attachmentIDs),
|
|
)
|
|
}
|
|
|
|
attachment, err := gtsClient.GetMediaAttachment(s.attachmentIDs[0])
|
|
if err != nil {
|
|
return fmt.Errorf("unable to retrieve the media attachment: %w", err)
|
|
}
|
|
|
|
s.printer.PrintMediaAttachment(attachment)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ShowExecutor) showMedia(gtsClient *client.Client) error {
|
|
if s.fromResourceType == "" {
|
|
return FlagNotSetError{flagText: flagFrom}
|
|
}
|
|
|
|
funcMap := map[string]func(*client.Client) error{
|
|
resourceStatus: s.showMediaFromStatus,
|
|
}
|
|
|
|
doFunc, ok := funcMap[s.fromResourceType]
|
|
if !ok {
|
|
return UnsupportedShowOperationError{
|
|
ResourceType: s.resourceType,
|
|
ShowFromResourceType: s.fromResourceType,
|
|
}
|
|
}
|
|
|
|
return doFunc(gtsClient)
|
|
}
|
|
|
|
func (s *ShowExecutor) showMediaFromStatus(gtsClient *client.Client) error {
|
|
if len(s.attachmentIDs) == 0 {
|
|
return FlagNotSetError{flagText: flagAttachmentID}
|
|
}
|
|
|
|
if s.statusID == "" {
|
|
return FlagNotSetError{flagText: flagStatusID}
|
|
}
|
|
|
|
status, err := gtsClient.GetStatus(s.statusID)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to retrieve the status: %w", err)
|
|
}
|
|
|
|
cacheDir := filepath.Join(
|
|
utilities.CalculateCacheDir(s.config.CacheDirectory, utilities.GetFQDN(gtsClient.Authentication.Instance)),
|
|
"media",
|
|
)
|
|
|
|
if err := utilities.EnsureDirectory(cacheDir); err != nil {
|
|
return fmt.Errorf("unable to ensure the existence of the directory %q: %w", cacheDir, err)
|
|
}
|
|
|
|
type media struct {
|
|
url string
|
|
mediaType string
|
|
}
|
|
|
|
attachmentsHashMap := make(map[string]media)
|
|
imageFiles := make([]string, 0)
|
|
videoFiles := make([]string, 0)
|
|
|
|
for _, statusAttachment := range status.MediaAttachments {
|
|
attachmentsHashMap[statusAttachment.ID] = media{
|
|
url: statusAttachment.URL,
|
|
mediaType: statusAttachment.Type,
|
|
}
|
|
}
|
|
|
|
for _, attachmentID := range s.attachmentIDs {
|
|
mediaObj, ok := attachmentsHashMap[attachmentID]
|
|
if !ok {
|
|
return UnknownMediaAttachmentError{AttachmentID: attachmentID}
|
|
}
|
|
|
|
split := strings.Split(mediaObj.url, "/")
|
|
filename := split[len(split)-1]
|
|
filePath := filepath.Join(cacheDir, filename)
|
|
|
|
fileExists, err := utilities.FileExists(filePath)
|
|
if err != nil {
|
|
return fmt.Errorf(
|
|
"unable to check if the media file is already downloaded for %s: %w",
|
|
attachmentID,
|
|
err,
|
|
)
|
|
}
|
|
|
|
if !fileExists {
|
|
if err := gtsClient.DownloadMedia(mediaObj.url, filePath); err != nil {
|
|
return fmt.Errorf(
|
|
"unable to download the media attachment for %s: %w",
|
|
attachmentID,
|
|
err,
|
|
)
|
|
}
|
|
}
|
|
|
|
switch mediaObj.mediaType {
|
|
case "image":
|
|
imageFiles = append(imageFiles, filePath)
|
|
case "video":
|
|
videoFiles = append(videoFiles, filePath)
|
|
}
|
|
}
|
|
|
|
if len(imageFiles) > 0 {
|
|
if err := utilities.OpenMedia(s.config.Integrations.ImageViewer, imageFiles); err != nil {
|
|
return fmt.Errorf("unable to open the image viewer: %w", err)
|
|
}
|
|
}
|
|
|
|
if len(videoFiles) > 0 {
|
|
if err := utilities.OpenMedia(s.config.Integrations.VideoPlayer, videoFiles); err != nil {
|
|
return fmt.Errorf("unable to open the video player: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|