Closes #3
This commit is contained in:
parent
a25279a835
commit
53105cbf20
10
README.md
10
README.md
@ -23,7 +23,11 @@ chmod +x blurp
|
|||||||
> also shifting sands). This is a best-effort explanation and might quickly be
|
> also shifting sands). This is a best-effort explanation and might quickly be
|
||||||
> out of date. Help welcome! 🟡
|
> out of date. Help welcome! 🟡
|
||||||
|
|
||||||
* `blurp delete` will get you rate limited by your own instance. I temporarily solved this by [turning off rate limiting](https://docs.gotosocial.org/en/latest/api/ratelimiting/#can-i-configure-the-rate-limit-can-i-just-turn-it-off), running the command and then turning rate limiting back on again. I could probably implement some backoff in the code but this is just easier.
|
* `blurp delete` would easily get you [rate
|
||||||
|
limited](https://docs.gotosocial.org/en/latest/api/ratelimiting/) by your own
|
||||||
|
instance due to the high volume of delete requests that are sent. To avoid
|
||||||
|
this, we send 1 request every second which avoids limits in the default
|
||||||
|
settings. If you have different defaults, pass `--rate/-r`.
|
||||||
|
|
||||||
* `blurp delete` will remove statuses but not media attachments *immediately*
|
* `blurp delete` will remove statuses but not media attachments *immediately*
|
||||||
for **local media**. Remote media is removed immediately. Unattached local
|
for **local media**. Remote media is removed immediately. Unattached local
|
||||||
@ -75,6 +79,10 @@ To delete all statuses older than *2 weeks*:
|
|||||||
blurp delete
|
blurp delete
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can use `--weeks/-w` to supply a value for "number of weeks". If you need
|
||||||
|
to send less requests, say, 1 request every 3 seconds, you can pass `-r 3`. See
|
||||||
|
`--help` for more.
|
||||||
|
|
||||||
> 🔴 **DANGER ZONE** 🔴
|
> 🔴 **DANGER ZONE** 🔴
|
||||||
|
|
||||||
## ACK
|
## ACK
|
||||||
|
157
blurp.go
157
blurp.go
@ -21,19 +21,22 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// getAccount returns the currently authenticated account.
|
var (
|
||||||
func getAccount(authClient *auth.Client) (*models.Account, error) {
|
user string
|
||||||
err := authClient.Wait()
|
weeks int
|
||||||
if err != nil {
|
rate int
|
||||||
return nil, err
|
)
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := authClient.Client.Accounts.AccountVerify(nil, authClient.Auth)
|
func init() {
|
||||||
if err != nil {
|
loginCmd.Flags().StringVarP(&user, "user", "u", "", "username@domain of account")
|
||||||
return nil, errors.WithStack(err)
|
rootCmd.AddCommand(loginCmd)
|
||||||
}
|
|
||||||
|
|
||||||
return resp.GetPayload(), nil
|
archiveCmd.Flags().IntVarP(&rate, "rate", "r", 1, "send a request every 'r' seconds")
|
||||||
|
rootCmd.AddCommand(archiveCmd)
|
||||||
|
|
||||||
|
deleteCmd.Flags().IntVarP(&weeks, "weeks", "w", 2, "keep statuses NEWER than no. of weeks")
|
||||||
|
deleteCmd.Flags().IntVarP(&rate, "rate", "r", 1, "send a request every 'r' seconds")
|
||||||
|
rootCmd.AddCommand(deleteCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// main is the command-line entrypoint.
|
// main is the command-line entrypoint.
|
||||||
@ -73,7 +76,7 @@ var archiveCmd = &cobra.Command{
|
|||||||
slog.Error("unable to retrieve account", "error", err)
|
slog.Error("unable to retrieve account", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
statuses, err := ReadAllPaged(authClient, acc.ID)
|
statuses, err := readAllPaged(authClient, acc.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("unable to download paged response", "error", err)
|
slog.Error("unable to download paged response", "error", err)
|
||||||
}
|
}
|
||||||
@ -127,6 +130,70 @@ var archiveCmd = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var deleteCmd = &cobra.Command{
|
||||||
|
Use: "delete",
|
||||||
|
Short: "Delete statuses like tears in the rain",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
authClient, err := auth.NewAuthClient(user)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("unable to create auth client", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info(fmt.Sprintf("keeping statuses NEWER than %d weeks", weeks))
|
||||||
|
|
||||||
|
acc, err := getAccount(authClient)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("unable to retrieve account", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
allStatuses, err := readAllPaged(authClient, acc.ID)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("unable to download paged response", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ISO8601 := "2006-01-02T15:04:05.000Z"
|
||||||
|
for _, status := range allStatuses {
|
||||||
|
t, err := time.Parse(ISO8601, status.CreatedAt)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("unable to parse status 'CreatedAt' value", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
numHours := time.Duration(168 * weeks)
|
||||||
|
if t.Before(time.Now().Add(-time.Hour * numHours)) {
|
||||||
|
_, err := authClient.Client.Statuses.StatusDelete(&statuses.StatusDeleteParams{
|
||||||
|
ID: status.ID,
|
||||||
|
}, authClient.Auth)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("unable to delete status", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info(fmt.Sprintf("deleted %s (created: %s)", status.ID, t.Format(time.DateOnly)))
|
||||||
|
|
||||||
|
time.Sleep(time.Duration(rate))
|
||||||
|
} else {
|
||||||
|
slog.Info(fmt.Sprintf("keeping %s (created: %s)", status.ID, t.Format(time.DateOnly)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAccount returns the currently authenticated account.
|
||||||
|
func getAccount(authClient *auth.Client) (*models.Account, error) {
|
||||||
|
err := authClient.Wait()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := authClient.Client.Accounts.AccountVerify(nil, authClient.Auth)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.GetPayload(), nil
|
||||||
|
}
|
||||||
|
|
||||||
// httpGetFile downloads a file from the internet.
|
// httpGetFile downloads a file from the internet.
|
||||||
func httpGetFile(filepath, url string) error {
|
func httpGetFile(filepath, url string) error {
|
||||||
out, err := os.Create(filepath)
|
out, err := os.Create(filepath)
|
||||||
@ -153,8 +220,8 @@ func httpGetFile(filepath, url string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseLinkMaxID extracts the `max_id` from the `next` link for paging to older items.
|
// parseLinkMaxID extracts the `max_id` from the `next` link for paging to older items.
|
||||||
func ParseLinkMaxID(linkHeader string) (*string, error) {
|
func parseLinkMaxID(linkHeader string) (*string, error) {
|
||||||
next := link.Parse(linkHeader)["next"]
|
next := link.Parse(linkHeader)["next"]
|
||||||
if next == nil {
|
if next == nil {
|
||||||
// No link header in that direction means end of results.
|
// No link header in that direction means end of results.
|
||||||
@ -171,7 +238,7 @@ func ParseLinkMaxID(linkHeader string) (*string, error) {
|
|||||||
return &nextMaxID, err
|
return &nextMaxID, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadAllPaged(authClient *auth.Client, accID string) ([]*models.Status, error) {
|
func readAllPaged(authClient *auth.Client, accID string) ([]*models.Status, error) {
|
||||||
var all []*models.Status
|
var all []*models.Status
|
||||||
var maxID *string
|
var maxID *string
|
||||||
|
|
||||||
@ -188,7 +255,7 @@ func ReadAllPaged(authClient *auth.Client, accID string) ([]*models.Status, erro
|
|||||||
return all, errors.WithStack(err)
|
return all, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
maxID, err = ParseLinkMaxID(resp.Link)
|
maxID, err = parseLinkMaxID(resp.Link)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("error parsing Link header", "error", err)
|
slog.Error("error parsing Link header", "error", err)
|
||||||
return all, errors.WithStack(err)
|
return all, errors.WithStack(err)
|
||||||
@ -198,64 +265,10 @@ func ReadAllPaged(authClient *auth.Client, accID string) ([]*models.Status, erro
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Duration(rate))
|
||||||
|
|
||||||
all = append(all, resp.GetPayload()...)
|
all = append(all, resp.GetPayload()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return all, nil
|
return all, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var deleteCmd = &cobra.Command{
|
|
||||||
Use: "delete",
|
|
||||||
Short: "Delete statuses like tears in the rain",
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
authClient, err := auth.NewAuthClient(user)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("unable to create auth client", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
acc, err := getAccount(authClient)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("unable to retrieve account", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
allStatuses, err := ReadAllPaged(authClient, acc.ID)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("unable to download paged response", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ISO8601 := "2006-01-02T15:04:05.000Z"
|
|
||||||
for _, status := range allStatuses {
|
|
||||||
t, err := time.Parse(ISO8601, status.CreatedAt)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("unable to parse status 'CreatedAt' value", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE(d1): 336 hours = 2 weeks
|
|
||||||
if t.Before(time.Now().Add(-time.Hour * 336)) {
|
|
||||||
_, err := authClient.Client.Statuses.StatusDelete(&statuses.StatusDeleteParams{
|
|
||||||
ID: status.ID,
|
|
||||||
}, authClient.Auth)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("unable to delete status", err)
|
|
||||||
}
|
|
||||||
slog.Info(fmt.Sprintf("deleted %s (created: %s)", status.ID, t.Format(time.DateOnly)))
|
|
||||||
} else {
|
|
||||||
slog.Info(fmt.Sprintf("keeping %s (created: %s)", status.ID, t.Format(time.DateOnly)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var user string
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.PersistentFlags().StringVarP(
|
|
||||||
&user, "user", "u", "", "username@domain of account",
|
|
||||||
)
|
|
||||||
|
|
||||||
rootCmd.AddCommand(loginCmd)
|
|
||||||
rootCmd.AddCommand(archiveCmd)
|
|
||||||
rootCmd.AddCommand(deleteCmd)
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user