This repository has been archived by the owner on Apr 15, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Sergey Kibish
committed
Apr 25, 2021
0 parents
commit e221b30
Showing
13 changed files
with
1,004 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
name: release | ||
|
||
on: | ||
push: | ||
tags: | ||
- '*' | ||
|
||
permissions: | ||
contents: write | ||
|
||
jobs: | ||
release: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- | ||
name: Checkout | ||
uses: actions/checkout@v2 | ||
with: | ||
fetch-depth: 0 | ||
- | ||
name: Set up Go | ||
uses: actions/setup-go@v2 | ||
with: | ||
go-version: 1.16 | ||
- | ||
name: Run GoReleaser | ||
uses: goreleaser/goreleaser-action@v2 | ||
with: | ||
version: latest | ||
args: release --rm-dist | ||
env: | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
on: | ||
push: | ||
branches: | ||
- main | ||
pull_request: | ||
branches: | ||
- main | ||
|
||
name: run tests | ||
jobs: | ||
test: | ||
strategy: | ||
matrix: | ||
go-version: [1.16.x] | ||
platform: [ubuntu-latest, macos-latest, windows-latest] | ||
runs-on: ${{ matrix.platform }} | ||
steps: | ||
- name: Install Go | ||
if: success() | ||
uses: actions/setup-go@v2 | ||
with: | ||
go-version: ${{ matrix.go-version }} | ||
- name: Checkout code | ||
uses: actions/checkout@v2 | ||
- name: Run tests | ||
run: go test -v -cover -race ./... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
dist/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# This is an example .goreleaser.yml file with some sane defaults. | ||
# Make sure to check the documentation at http://goreleaser.com | ||
builds: | ||
- env: | ||
- CGO_ENABLED=0 | ||
goos: | ||
- linux | ||
- windows | ||
- darwin | ||
goarm: | ||
- 6 | ||
- 7 | ||
archives: | ||
- replacements: | ||
darwin: Darwin | ||
linux: Linux | ||
windows: Windows | ||
386: i386 | ||
amd64: x86_64 | ||
checksum: | ||
name_template: 'checksums.txt' | ||
snapshot: | ||
name_template: "{{ .Tag }}-next" | ||
changelog: | ||
sort: asc | ||
filters: | ||
exclude: | ||
- '^docs:' | ||
- '^test:' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2021 Sergey Kibish | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
# Twitter Cleaner | ||
|
||
 | ||
 | ||
[](https://goreportcard.com/report/github.com/skibish/twitter-cleaner) | ||
|
||
Clean your Twitter. | ||
|
||
Once in 24 hours all tweets that are older than half a year (by default) will be deleted, un-retweeted and un-favorited from your feed. | ||
|
||
## Motivation | ||
|
||
I observed that tweets liveability is a few days maximum. | ||
Why then I need to store them all in my timeline? | ||
For me Twitter is a place to interact with others and not to use it as a diary for archeologists. | ||
|
||
If you would like to do regular cleanups too, this tool is for you. | ||
|
||
## Install | ||
|
||
Download binary from [releases page](https://github.com/skibish/twitter-cleaner/releases). | ||
|
||
If you have Go: | ||
|
||
```sh | ||
go get github.com/skibish/twitter-cleaner | ||
``` | ||
|
||
## Example | ||
|
||
```sh | ||
$ ./twitter-retention \ | ||
-access-token aaa \ | ||
-access-token-secret ttt \ | ||
-consumer-key kkk \ | ||
-consumer-secret xxx | ||
2021/04/25 13:19:53 successfully started | ||
2021/04/25 13:20:52 scanned through 44 timeline tweets | ||
2021/04/25 13:20:52 DELETING XXXXXXXXXXXXXX240 | ||
2021/04/25 13:20:52 UNRETWEETING XXXXXXXXXXXXXX624 | ||
2021/04/25 13:20:53 scanned through 0 timeline tweets | ||
2021/04/25 13:20:53 scanned through 199 favorite tweets | ||
2021/04/25 13:20:54 scanned through 105 favorite tweets | ||
2021/04/25 13:20:54 UNFAVORITING XXXXXXXXXXXXXX416 | ||
2021/04/25 13:20:54 UNFAVORITING XXXXXXXXXXXXXX183 | ||
2021/04/25 13:20:54 UNFAVORITING XXXXXXXXXXXXXX100 | ||
2021/04/25 13:20:54 UNFAVORITING XXXXXXXXXXXXXX225 | ||
2021/04/25 13:20:55 UNFAVORITING XXXXXXXXXXXXXX508 | ||
2021/04/25 13:20:55 UNFAVORITING XXXXXXXXXXXXXX317 | ||
^C | ||
2021/04/25 13:21:20 shutdown | ||
``` | ||
|
||
## Usage | ||
|
||
```sh | ||
./twitter-cleaner: | ||
-access-token string | ||
Access token | ||
-access-token-secret string | ||
Access token secret | ||
-check-interval duration | ||
Cleanup interval (default 24h0m0s) | ||
-consumer-key string | ||
Consumer key | ||
-consumer-secret string | ||
Consumer secret | ||
-dry-run | ||
Check that something can be deleted, no real deletion is made | ||
-tweet-age duration | ||
Tweets older than this duration will be deleted (default 4380h0m0s) | ||
-v Show version | ||
``` | ||
|
||
You can [find out here](https://developer.twitter.com/en/docs/basics/authentication/guides/access-tokens) how to create all needed tokens. | ||
|
||
## Development | ||
|
||
```sh | ||
go get | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"sync" | ||
"time" | ||
|
||
"github.com/ChimeraCoder/anaconda" | ||
) | ||
|
||
type Cleaner struct { | ||
twitter *Twitter | ||
tweetAge time.Duration | ||
checkInterval time.Duration | ||
dryRun bool | ||
userID int64 | ||
ticker *time.Ticker | ||
shutdown chan bool | ||
} | ||
|
||
func NewCleaner(twitter *Twitter, tweetAge, checkInterval time.Duration, dryRun bool) *Cleaner { | ||
return &Cleaner{ | ||
twitter: twitter, | ||
tweetAge: tweetAge, | ||
checkInterval: checkInterval, | ||
dryRun: dryRun, | ||
ticker: time.NewTicker(checkInterval), | ||
shutdown: make(chan bool), | ||
} | ||
} | ||
|
||
func (c *Cleaner) Init() error { | ||
userID, err := c.twitter.Self() | ||
if err != nil { | ||
return fmt.Errorf("failed to get user ID: %w", err) | ||
} | ||
c.userID = userID | ||
|
||
return nil | ||
} | ||
|
||
func (c *Cleaner) Start() error { | ||
var wg sync.WaitGroup | ||
|
||
for { | ||
select { | ||
case <-c.ticker.C: | ||
wg.Add(2) | ||
if err := c.cleanTimeline(); err != nil { | ||
return fmt.Errorf("failed to clean timeline: %w", err) | ||
} | ||
wg.Done() | ||
|
||
if err := c.cleanFavorites(); err != nil { | ||
return fmt.Errorf("failed to clean favorites: %w", err) | ||
} | ||
wg.Done() | ||
case <-c.shutdown: | ||
wg.Wait() | ||
return nil | ||
} | ||
} | ||
} | ||
|
||
func (c *Cleaner) Stop() { | ||
c.ticker.Stop() | ||
c.shutdown <- true | ||
} | ||
|
||
func (c *Cleaner) cleanTimeline() error { | ||
var oldestTweetID int64 | ||
for { | ||
tweets, err := c.twitter.GetUserTimeline(oldestTweetID) | ||
if err != nil { | ||
return fmt.Errorf("failed to get tweets from user timeline: %w", err) | ||
} | ||
|
||
log.Printf("scanned through %d timeline tweets", len(tweets)) | ||
|
||
if len(tweets) <= 1 { | ||
break | ||
} | ||
|
||
// get oldest tweet ID from the response | ||
// and set it as a limit for the next call | ||
oldestTweetID = tweets[len(tweets)-1].Id | ||
|
||
for _, tweet := range tweets { | ||
if err := c.remove(tweet); err != nil { | ||
return fmt.Errorf("failed to remove tweet: %w", err) | ||
} | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (c *Cleaner) cleanFavorites() error { | ||
var oldestTweetID int64 | ||
for { | ||
tweets, err := c.twitter.GetUserFavorites(oldestTweetID) | ||
if err != nil { | ||
return fmt.Errorf("failed to get tweets from favorites: %w", err) | ||
} | ||
|
||
log.Printf("scanned through %d favorite tweets", len(tweets)) | ||
|
||
if len(tweets) <= 1 { | ||
break | ||
} | ||
|
||
// get oldest tweet ID from the response | ||
// and set it as a limit for the next call | ||
oldestTweetID = tweets[len(tweets)-1].Id | ||
|
||
for _, tweet := range tweets { | ||
if err := c.remove(tweet); err != nil { | ||
return fmt.Errorf("failed to remove tweet: %w", err) | ||
} | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (c *Cleaner) remove(tweet anaconda.Tweet) error { | ||
createdAt, err := tweet.CreatedAtTime() | ||
if err != nil { | ||
return fmt.Errorf("failed to get createdAt time of a tweet: %w", err) | ||
} | ||
|
||
// if duration is less than retention period specified, | ||
// skip it | ||
if time.Since(createdAt) < c.tweetAge { | ||
return nil | ||
} | ||
|
||
if tweet.Favorited { | ||
log.Printf("UNFAVORITING\t%d", tweet.Id) | ||
|
||
if !c.dryRun { | ||
if err := c.twitter.UnFavorite(tweet.Id); err != nil { | ||
return fmt.Errorf("failed to unfavorite the tweet %d: %w", tweet.Id, err) | ||
} | ||
} | ||
} | ||
|
||
if tweet.Retweeted { | ||
log.Printf("UNRETWEETING\t%d", tweet.Id) | ||
|
||
if !c.dryRun { | ||
if err := c.twitter.UnRetweet(tweet.Id); err != nil { | ||
return fmt.Errorf("failed to unretweet the tweet %d: %w", tweet.Id, err) | ||
} | ||
} | ||
} | ||
|
||
// if favorited/retweeted tweet is not a users tweet, | ||
// return earlier | ||
if tweet.User.Id != c.userID { | ||
return nil | ||
} | ||
|
||
log.Printf("DELETING\t%d", tweet.Id) | ||
if !c.dryRun { | ||
if err := c.twitter.Delete(tweet.Id); err != nil { | ||
return fmt.Errorf("failed to delete the tweet %d: %w", tweet.Id, err) | ||
} | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.