Skip to content
This repository has been archived by the owner on Feb 2, 2023. It is now read-only.

Commit

Permalink
Delete direct messages (and related cleanup)
Browse files Browse the repository at this point in the history
- Clarify variable naming
- Use “likes” in output/docs rather than “favourites”
- Fix some RuboCop warnings
- Avoid checking first page of results more than once
  • Loading branch information
MikeMcQuaid committed May 18, 2018
1 parent acd059b commit 752293a
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 42 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# TwitterDelete
TwitterDelete is a small application to delete your old, unpopular tweets.
TwitterDelete is a small application to delete your old, unpopular tweets, likes and direct messages.

## Features
- Delete, unfavorite and unretweet tweets
- Keep tweets based on age, retweet or favourite count
- Delete, unlike and unretweet tweets
- Delete direct messages
- Keep tweets/messages based on age and tweeks based on retweet or favourite count
- Delete tweets no longer exposed by Twitter API from a downloaded Twitter archive file

## Usage
Expand All @@ -28,7 +29,7 @@ Now run TwitterDelete:
```

## Status
Works for deleting relevant tweets. I've deleted all my tweets and don't really use Twitter any more so am not actively working on this but will accept pull requests.
Works for deleting relevant tweet, likes and messages. I've deleted my old tweets and am not actively working on this but I will accept pull requests.

[![Build Status](https://travis-ci.org/MikeMcQuaid/TwitterDelete.svg?branch=master)](https://travis-ci.org/MikeMcQuaid/TwitterDelete)

Expand Down
107 changes: 69 additions & 38 deletions twitter_delete.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,31 @@
require "csv"
require "dotenv"

MAX_API_TWEETS = 3200
MAX_TWEETS_PER_PAGE = 250.0
MAX_TWEETS_PER_REQUEST = 100
MAX_API_TWEETS_MESSAGES = 3200
MAX_TWEETS_MESSAGES_PER_PAGE = 200.0
MAX_TWEETS_MESSAGES_PER_REQUEST = 100
MAX_LIKES_PER_PAGE = 100.0

Dotenv.load

@options = Trollop::options do
opt :force, "Actually delete/unfavourite/unretweet tweets", type: :boolean, default: false
@options = Trollop.options do
opt :force, "Actually delete/unlike/unretweet tweets/messages", type: :boolean, default: false
opt :user, "The Twitter username to purge", type: :string, default: ENV["TWITTER_USER"]
opt :csv, "Twitter archive tweets.csv file", type: :string
opt :days, "Keep tweets under this many days old", default: 28
opt :olds, "Keep tweets more than this many days old", default: 9999
opt :days, "Keep tweets/likes/messages under this many days old", default: 28
opt :olds, "Keep tweets/likes/messages more than this many days old", default: 9999
opt :rts, "Keep tweet with this many retweets", default: 5
opt :favs, "Keep tweets with this many favourites", default: 5
opt :favs, "Keep tweets with this many likes", default: 5
end

Trollop::die :user, "must be set" if @options[:user].to_s.empty?
Trollop.die :user, "must be set" if @options[:user].to_s.empty?
if @options[:csv_given] && !File.exist?(@options[:csv])
Trollop::die :csv, "must be a file that exists"
Trollop.die :csv, "must be a file that exists"
end

[ "TWITTER_CONSUMER_KEY", "TWITTER_CONSUMER_SECRET",
"TWITTER_ACCESS_TOKEN", "TWITTER_ACCESS_TOKEN_SECRET"].each do |env|
Trollop::die "#{env} environment variable must be set" unless ENV[env]
%w[TWITTER_CONSUMER_KEY TWITTER_CONSUMER_SECRET
TWITTER_ACCESS_TOKEN TWITTER_ACCESS_TOKEN_SECRET].each do |env|
Trollop.die "#{env} environment variable must be set" unless ENV[env]
end

@client = Twitter::REST::Client.new do |config|
Expand All @@ -42,15 +43,18 @@
config.access_token_secret = ENV["TWITTER_ACCESS_TOKEN_SECRET"]
end

@oldest_tweet_time_to_keep = Time.now - @options[:days]*24*60*60
@newest_tweet_time_to_keep = Time.now - @options[:olds]*24*60*60
@oldest_tweet_time_to_keep = Time.now - @options[:days] * 24 * 60 * 60
@newest_tweet_time_to_keep = Time.now - @options[:olds] * 24 * 60 * 60

def too_new? tweet
def too_new?(tweet)
tweet.created_at > @oldest_tweet_time_to_keep || tweet.created_at < @newest_tweet_time_to_keep
end

def too_new_or_popular? tweet
return true if too_new? tweet
def too_new_or_popular?(tweet_or_message)
return true if too_new? tweet_or_message
return false unless tweet_or_message.is_a?(Twitter::Tweet)

tweet = tweet_or_message
return false if tweet.retweeted?
return false if tweet.text.start_with? "RT @"

Expand All @@ -60,14 +64,14 @@ def too_new_or_popular? tweet
end

if tweet.favorite_count >= @options[:favs]
puts "Ignoring tweet: too favourited: #{tweet.text}"
puts "Ignoring tweet: too liked: #{tweet.text}"
return true
end

false
end

def api_call method, *args
def api_call(method, *args)
@client.send method, *args
rescue Twitter::Error::TooManyRequests => error
puts "Rate limit exceeded; waiting until #{error.rate_limit.reset_at}"
Expand All @@ -76,27 +80,35 @@ def api_call method, *args
end

user = api_call :user, @options[:username]
tweets_to_unfavourite = []
tweets_to_unlike = []
tweets_to_delete = []
messages_to_delete = []

puts "==> Checking favourites..."
total_favorites = [user.favorites_count, MAX_API_TWEETS].min
oldest_favorites_page = (total_favorites / MAX_TWEETS_PER_PAGE).ceil
puts "==> Checking likes..."
total_likes = [user.favorites_count, MAX_API_TWEETS_MESSAGES].min
oldest_likes_page = (total_likes / MAX_LIKES_PER_PAGE).ceil

oldest_favorites_page.downto(0) do |page|
tweets = api_call :favorites, count: MAX_TWEETS_PER_PAGE, page: page
tweets_to_unfavourite += tweets.reject(&method(:too_new?))
oldest_likes_page.downto(1) do |page|
tweets = api_call :favorites, count: MAX_LIKES_PER_PAGE, page: page
tweets_to_unlike += tweets.reject(&method(:too_new?))
end

puts "Checking timeline..."
total_tweets = [user.statuses_count, MAX_API_TWEETS].min
oldest_tweets_page = (total_tweets / MAX_TWEETS_PER_PAGE).ceil
puts "==> Checking timeline..."
total_tweets = [user.statuses_count, MAX_API_TWEETS_MESSAGES].min
oldest_tweets_page = (total_tweets / MAX_TWEETS_MESSAGES_PER_PAGE).ceil

oldest_tweets_page.downto(0) do |page|
tweets = api_call :user_timeline, count: MAX_TWEETS_PER_PAGE, page: page
oldest_tweets_page.downto(1) do |page|
tweets = api_call :user_timeline, count: MAX_TWEETS_MESSAGES_PER_PAGE, page: page
tweets_to_delete += tweets.reject(&method(:too_new_or_popular?))
end

puts "Checking messages..."
oldest_messages_page = (MAX_API_TWEETS_MESSAGES / MAX_TWEETS_MESSAGES_PER_PAGE).ceil
oldest_messages_page.downto(1) do |page|
messages = api_call :direct_messages, count: MAX_TWEETS_MESSAGES_PER_PAGE, page: page
messages_to_delete += messages.reject(&method(:too_new_or_popular?))
end

if @options[:csv_given]
puts "==> Checking archive CSV..."
csv_tweet_ids = []
Expand All @@ -106,20 +118,20 @@ def api_call method, *args
csv_tweet_ids << tweet_id
end

csv_tweet_ids.each_slice(MAX_TWEETS_PER_REQUEST) do |tweet_ids|
csv_tweet_ids.each_slice(MAX_TWEETS_MESSAGES_PER_REQUEST) do |tweet_ids|
tweets = api_call :statuses, tweet_ids
tweets_to_delete += tweets.reject(&method(:too_new_or_popular?))
end
end

if !@options[:force]
puts "==> To unfavorite #{tweets_to_unfavourite.size} and delete #{tweets_to_delete.size} tweets, re-run the command with --force"
unless @options[:force]
puts "==> To unlike #{tweets_to_unlike.size} and delete #{tweets_to_delete.size} tweets, re-run the command with --force"
exit 0
end

puts "==> Unfavoriting #{tweets_to_unfavourite.size} tweets"
puts "==> Unliking #{tweets_to_unlike.size} tweets"
tweets_not_found = []
tweets_to_unfavourite.each_slice(MAX_TWEETS_PER_REQUEST) do |tweets|
tweets_to_unlike.each_slice(MAX_TWEETS_MESSAGES_PER_REQUEST) do |tweets|
begin
api_call :unfavorite, tweets
rescue Twitter::Error::NotFound
Expand All @@ -128,7 +140,7 @@ def api_call method, *args
end

puts "==> Deleting #{tweets_to_delete.size} tweets"
tweets_to_delete.each_slice(MAX_TWEETS_PER_REQUEST) do |tweets|
tweets_to_delete.each_slice(MAX_TWEETS_MESSAGES_PER_REQUEST) do |tweets|
begin
api_call :destroy_status, tweets
rescue Twitter::Error::NotFound
Expand All @@ -140,5 +152,24 @@ def api_call method, *args
begin
api_call :destroy_status, tweet
rescue Twitter::Error::NotFound
nil
end
end

puts "==> Deleting #{messages_to_delete.size} messages"
messages_not_found = []
messages_to_delete.each_slice(MAX_TWEETS_MESSAGES_PER_REQUEST) do |messages|
begin
api_call :destroy_direct_message, messages
rescue Twitter::Error::NotFound
messages_not_found += messages
end
end

messages_not_found.each do |message|
begin
api_call :destroy_direct_message, message
rescue Twitter::Error::NotFound
nil
end
end

0 comments on commit 752293a

Please sign in to comment.