Skip to content
This repository was archived by the owner on Apr 15, 2023. It is now read-only.

Commit e221b30

Browse files
author
Sergey Kibish
committed
initial commit
0 parents  commit e221b30

File tree

13 files changed

+1004
-0
lines changed

13 files changed

+1004
-0
lines changed

.github/workflows/release.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: release
2+
3+
on:
4+
push:
5+
tags:
6+
- '*'
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
release:
13+
runs-on: ubuntu-latest
14+
steps:
15+
-
16+
name: Checkout
17+
uses: actions/checkout@v2
18+
with:
19+
fetch-depth: 0
20+
-
21+
name: Set up Go
22+
uses: actions/setup-go@v2
23+
with:
24+
go-version: 1.16
25+
-
26+
name: Run GoReleaser
27+
uses: goreleaser/goreleaser-action@v2
28+
with:
29+
version: latest
30+
args: release --rm-dist
31+
env:
32+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/test.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
on:
2+
push:
3+
branches:
4+
- main
5+
pull_request:
6+
branches:
7+
- main
8+
9+
name: run tests
10+
jobs:
11+
test:
12+
strategy:
13+
matrix:
14+
go-version: [1.16.x]
15+
platform: [ubuntu-latest, macos-latest, windows-latest]
16+
runs-on: ${{ matrix.platform }}
17+
steps:
18+
- name: Install Go
19+
if: success()
20+
uses: actions/setup-go@v2
21+
with:
22+
go-version: ${{ matrix.go-version }}
23+
- name: Checkout code
24+
uses: actions/checkout@v2
25+
- name: Run tests
26+
run: go test -v -cover -race ./...

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dist/

.goreleaser.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# This is an example .goreleaser.yml file with some sane defaults.
2+
# Make sure to check the documentation at http://goreleaser.com
3+
builds:
4+
- env:
5+
- CGO_ENABLED=0
6+
goos:
7+
- linux
8+
- windows
9+
- darwin
10+
goarm:
11+
- 6
12+
- 7
13+
archives:
14+
- replacements:
15+
darwin: Darwin
16+
linux: Linux
17+
windows: Windows
18+
386: i386
19+
amd64: x86_64
20+
checksum:
21+
name_template: 'checksums.txt'
22+
snapshot:
23+
name_template: "{{ .Tag }}-next"
24+
changelog:
25+
sort: asc
26+
filters:
27+
exclude:
28+
- '^docs:'
29+
- '^test:'

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 Sergey Kibish
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Twitter Cleaner
2+
3+
![Test](https://github.com/skibish/twitter-cleaner/workflows/run%20tests/badge.svg)
4+
![Release](https://github.com/skibish/twitter-cleaner/workflows/release/badge.svg)
5+
[![Go Report Card](https://goreportcard.com/badge/github.com/skibish/twitter-cleaner)](https://goreportcard.com/report/github.com/skibish/twitter-cleaner)
6+
7+
Clean your Twitter.
8+
9+
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.
10+
11+
## Motivation
12+
13+
I observed that tweets liveability is a few days maximum.
14+
Why then I need to store them all in my timeline?
15+
For me Twitter is a place to interact with others and not to use it as a diary for archeologists.
16+
17+
If you would like to do regular cleanups too, this tool is for you.
18+
19+
## Install
20+
21+
Download binary from [releases page](https://github.com/skibish/twitter-cleaner/releases).
22+
23+
If you have Go:
24+
25+
```sh
26+
go get github.com/skibish/twitter-cleaner
27+
```
28+
29+
## Example
30+
31+
```sh
32+
$ ./twitter-retention \
33+
-access-token aaa \
34+
-access-token-secret ttt \
35+
-consumer-key kkk \
36+
-consumer-secret xxx
37+
2021/04/25 13:19:53 successfully started
38+
2021/04/25 13:20:52 scanned through 44 timeline tweets
39+
2021/04/25 13:20:52 DELETING XXXXXXXXXXXXXX240
40+
2021/04/25 13:20:52 UNRETWEETING XXXXXXXXXXXXXX624
41+
2021/04/25 13:20:53 scanned through 0 timeline tweets
42+
2021/04/25 13:20:53 scanned through 199 favorite tweets
43+
2021/04/25 13:20:54 scanned through 105 favorite tweets
44+
2021/04/25 13:20:54 UNFAVORITING XXXXXXXXXXXXXX416
45+
2021/04/25 13:20:54 UNFAVORITING XXXXXXXXXXXXXX183
46+
2021/04/25 13:20:54 UNFAVORITING XXXXXXXXXXXXXX100
47+
2021/04/25 13:20:54 UNFAVORITING XXXXXXXXXXXXXX225
48+
2021/04/25 13:20:55 UNFAVORITING XXXXXXXXXXXXXX508
49+
2021/04/25 13:20:55 UNFAVORITING XXXXXXXXXXXXXX317
50+
^C
51+
2021/04/25 13:21:20 shutdown
52+
```
53+
54+
## Usage
55+
56+
```sh
57+
./twitter-cleaner:
58+
-access-token string
59+
Access token
60+
-access-token-secret string
61+
Access token secret
62+
-check-interval duration
63+
Cleanup interval (default 24h0m0s)
64+
-consumer-key string
65+
Consumer key
66+
-consumer-secret string
67+
Consumer secret
68+
-dry-run
69+
Check that something can be deleted, no real deletion is made
70+
-tweet-age duration
71+
Tweets older than this duration will be deleted (default 4380h0m0s)
72+
-v Show version
73+
```
74+
75+
You can [find out here](https://developer.twitter.com/en/docs/basics/authentication/guides/access-tokens) how to create all needed tokens.
76+
77+
## Development
78+
79+
```sh
80+
go get
81+
```

cleaner.go

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"sync"
7+
"time"
8+
9+
"github.com/ChimeraCoder/anaconda"
10+
)
11+
12+
type Cleaner struct {
13+
twitter *Twitter
14+
tweetAge time.Duration
15+
checkInterval time.Duration
16+
dryRun bool
17+
userID int64
18+
ticker *time.Ticker
19+
shutdown chan bool
20+
}
21+
22+
func NewCleaner(twitter *Twitter, tweetAge, checkInterval time.Duration, dryRun bool) *Cleaner {
23+
return &Cleaner{
24+
twitter: twitter,
25+
tweetAge: tweetAge,
26+
checkInterval: checkInterval,
27+
dryRun: dryRun,
28+
ticker: time.NewTicker(checkInterval),
29+
shutdown: make(chan bool),
30+
}
31+
}
32+
33+
func (c *Cleaner) Init() error {
34+
userID, err := c.twitter.Self()
35+
if err != nil {
36+
return fmt.Errorf("failed to get user ID: %w", err)
37+
}
38+
c.userID = userID
39+
40+
return nil
41+
}
42+
43+
func (c *Cleaner) Start() error {
44+
var wg sync.WaitGroup
45+
46+
for {
47+
select {
48+
case <-c.ticker.C:
49+
wg.Add(2)
50+
if err := c.cleanTimeline(); err != nil {
51+
return fmt.Errorf("failed to clean timeline: %w", err)
52+
}
53+
wg.Done()
54+
55+
if err := c.cleanFavorites(); err != nil {
56+
return fmt.Errorf("failed to clean favorites: %w", err)
57+
}
58+
wg.Done()
59+
case <-c.shutdown:
60+
wg.Wait()
61+
return nil
62+
}
63+
}
64+
}
65+
66+
func (c *Cleaner) Stop() {
67+
c.ticker.Stop()
68+
c.shutdown <- true
69+
}
70+
71+
func (c *Cleaner) cleanTimeline() error {
72+
var oldestTweetID int64
73+
for {
74+
tweets, err := c.twitter.GetUserTimeline(oldestTweetID)
75+
if err != nil {
76+
return fmt.Errorf("failed to get tweets from user timeline: %w", err)
77+
}
78+
79+
log.Printf("scanned through %d timeline tweets", len(tweets))
80+
81+
if len(tweets) <= 1 {
82+
break
83+
}
84+
85+
// get oldest tweet ID from the response
86+
// and set it as a limit for the next call
87+
oldestTweetID = tweets[len(tweets)-1].Id
88+
89+
for _, tweet := range tweets {
90+
if err := c.remove(tweet); err != nil {
91+
return fmt.Errorf("failed to remove tweet: %w", err)
92+
}
93+
}
94+
}
95+
96+
return nil
97+
}
98+
99+
func (c *Cleaner) cleanFavorites() error {
100+
var oldestTweetID int64
101+
for {
102+
tweets, err := c.twitter.GetUserFavorites(oldestTweetID)
103+
if err != nil {
104+
return fmt.Errorf("failed to get tweets from favorites: %w", err)
105+
}
106+
107+
log.Printf("scanned through %d favorite tweets", len(tweets))
108+
109+
if len(tweets) <= 1 {
110+
break
111+
}
112+
113+
// get oldest tweet ID from the response
114+
// and set it as a limit for the next call
115+
oldestTweetID = tweets[len(tweets)-1].Id
116+
117+
for _, tweet := range tweets {
118+
if err := c.remove(tweet); err != nil {
119+
return fmt.Errorf("failed to remove tweet: %w", err)
120+
}
121+
}
122+
}
123+
124+
return nil
125+
}
126+
127+
func (c *Cleaner) remove(tweet anaconda.Tweet) error {
128+
createdAt, err := tweet.CreatedAtTime()
129+
if err != nil {
130+
return fmt.Errorf("failed to get createdAt time of a tweet: %w", err)
131+
}
132+
133+
// if duration is less than retention period specified,
134+
// skip it
135+
if time.Since(createdAt) < c.tweetAge {
136+
return nil
137+
}
138+
139+
if tweet.Favorited {
140+
log.Printf("UNFAVORITING\t%d", tweet.Id)
141+
142+
if !c.dryRun {
143+
if err := c.twitter.UnFavorite(tweet.Id); err != nil {
144+
return fmt.Errorf("failed to unfavorite the tweet %d: %w", tweet.Id, err)
145+
}
146+
}
147+
}
148+
149+
if tweet.Retweeted {
150+
log.Printf("UNRETWEETING\t%d", tweet.Id)
151+
152+
if !c.dryRun {
153+
if err := c.twitter.UnRetweet(tweet.Id); err != nil {
154+
return fmt.Errorf("failed to unretweet the tweet %d: %w", tweet.Id, err)
155+
}
156+
}
157+
}
158+
159+
// if favorited/retweeted tweet is not a users tweet,
160+
// return earlier
161+
if tweet.User.Id != c.userID {
162+
return nil
163+
}
164+
165+
log.Printf("DELETING\t%d", tweet.Id)
166+
if !c.dryRun {
167+
if err := c.twitter.Delete(tweet.Id); err != nil {
168+
return fmt.Errorf("failed to delete the tweet %d: %w", tweet.Id, err)
169+
}
170+
}
171+
172+
return nil
173+
}

0 commit comments

Comments
 (0)