-
-
Save tolsadus/1af3b7e29f17912f7deb1830fbad03fe to your computer and use it in GitHub Desktop.
A twitter bot, here designed to work with some active record models, but the logic is there.
This file contains hidden or 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
require 'twitter' | |
require 'logger' | |
# ๐ต patch search results, because they sucks | |
module Twitter | |
class SearchResults | |
attr_reader :rate_limit | |
def initialize(request) | |
@client = request.client | |
@request_method = request.verb | |
@path = request.path | |
@options = request.options | |
@collection = [] | |
@rate_limit = request.rate_limit | |
self.attrs = request.perform | |
end | |
end | |
end | |
# | |
# Thaaat's my boy ๐ | |
# | |
module Evil | |
class Bot | |
FRIEND_MAX_COUNT = 1500 | |
PUBLISH_HOURS = [20] | |
LOG_LEVEL = Logger::DEBUG | |
RETWEET_MIN = 50 | |
FAVORITE_MIN = 30 | |
DO_NOT_TRACK = [1344745638, 2818911550] | |
DEBUG = Rails.env.development? | |
LOG_PATH = './evil_bot.log' | |
def initialize(screen_name) | |
@l = Logger.new(LOG_PATH, 'monthly') | |
@l.level = LOG_LEVEL | |
@screen_name = screen_name | |
@account = Account.where(name: screen_name).first | |
if @account and @account.is_complete? | |
โฐ | |
else | |
@l.error "User not found or account incomplete" | |
end | |
end | |
def โฐ | |
@๐จ = { | |
consumer_key: @account.consumer_key, | |
consumer_secret: @account.consumer_secret, | |
access_token: @account.access_token, | |
access_token_secret: @account.access_token_secret | |
} | |
@search_hashtags = @account.search_queries.where(kind: :search).pluck(:query) | |
@tweet_hashtags = @account.search_queries.where(kind: :tweet).pluck(:query) | |
@limits = { | |
new_friendship: 10, #TODO remove that -> 15 | |
destroy_friendship: 15, | |
retweet: 15, | |
starring: 15, | |
search: 180, | |
crawl_user: 15 | |
} | |
@limits.each do |label, limit| | |
create_method "mark_#{label}".to_sym do | |
if @limits[label] > 0 | |
@limits[label] -= 1 | |
return true | |
else | |
return false | |
end | |
end | |
create_method "can_#{label}?".to_sym do | |
return @limits[label] > 0 | |
end | |
end | |
@client = Twitter::REST::Client.new(@๐จ) | |
@user = @client.user | |
๐ท and ๐พ | |
@followed_ids = @client.friend_ids.to_h[:ids] | |
@following_me = @client.follower_ids.to_h[:ids] - DO_NOT_TRACK | |
@can_be_removed = (@followed_ids - @following_me).reverse | |
@starred_ids = @client.favorites({count: 100}).map{|fav| fav.id} | |
@rt_ids = @client.retweeted_by_me({count: 200, exclude_replies: true}).map{|rt| rt.id} | |
@followed_cache = @followed_ids | |
@followed_users = FollowedUser.where(account_id: @account.id).first_or_create | |
@seen_users = (@followed_users.users || []) + (@followed_ids + @following_me) | |
end | |
# ๐ | |
def create_method(name, &block) | |
self.class.send(:define_method, name, &block) | |
end | |
# โ๏ธ | |
def make_some_friends | |
request = @search_hashtags.join(" OR ") | |
if can_search? | |
results = @client.search(request, {lang: "fr"}).take(100) | |
results.each do |tweet| | |
return unless mark_search | |
unless follow_tweet(tweet) | |
unless star_tweet(tweet) | |
return | |
end | |
end | |
end | |
end | |
end | |
# EVER AND EVER ! | |
def make_ever_more_friends | |
if can_search? and can_new_friendship? and can_crawl_user? | |
mark_crawl_user | |
@following_me.each do |uid| | |
mark_crawl_user | |
(@client.follower_ids(uid).to_h[:ids] - DO_NOT_TRACK).each do |to_follow| | |
return unless follow_user(to_follow) and can_crawl_user? | |
end | |
end | |
else | |
end | |
end | |
# Time to go my friend, see you on the other side ! | |
def break_friendships | |
if can_search? and can_destroy_friendship? and can_crawl_user? and @user.friends_count > FRIEND_MAX_COUNT | |
mark_crawl_user | |
@can_be_removed.each do |uid| | |
return unless destroy_friendship(uid) and can_crawl_user? | |
end | |
else | |
end | |
end | |
# Will try to follow the tweet, if it has to be | |
def follow_user user_id | |
return false unless can_new_friendship? | |
if !@seen_users.include?(user_id) | |
mark_new_friendship | |
@client.follow!(user_id) unless DEBUG | |
@followed_ids << user_id | |
@seen_users << user_id | |
end | |
true | |
end | |
# Will try to destroy the friendship, if it has to be | |
def destroy_friendship user_id | |
return false unless can_destroy_friendship? | |
mark_destroy_friendship | |
@client.unfollow(user_id) unless DEBUG | |
@followed_ids -= [user_id] | |
@can_be_removed -= [user_id] | |
true | |
end | |
# Will try to follow the tweet, if it has to be | |
def follow_tweet tweet | |
return false unless can_new_friendship? | |
follow_user(tweet.user.id) | |
true | |
end | |
# Will fetch if this tweet has to be starred | |
def star_tweet tweet | |
return false unless can_starring? | |
user = tweet.user | |
if !@seen_users.include?(user.id) and !@starred_ids.include?(tweet.id) and can_starring? | |
begin | |
mark_starring | |
@client.favorite!(tweet) unless DEBUG | |
rescue Twitter::Error::AlreadyFavorited => error | |
@starred_ids << tweet.id | |
@l.error("Favorite") { error } | |
end | |
end | |
true | |
end | |
# We set an unique retweet time, in order to avoid spamming | |
def time_to_retweet? | |
PUBLISH_HOURS.include?(Time.now.hour) and Time.now.min < 20 | |
end | |
# Will try to publish something nice | |
def say_interesting_things | |
if time_to_retweet? | |
results = @client.search(@tweet_hashtags.join(" OR ")).take(100) | |
tweets = select_tweets(results) | |
tweets = tweets.reject{|t| @rt_ids.include? t.id} | |
tweets.sort! { |x,y| y.retweet_count <=> x.retweet_count } | |
begin | |
mark_retweet && @client.retweet(tweets.first) unless DEBUG | |
rescue Twitter::Error::Forbidden => error | |
@l.error error | |
return false | |
end | |
else | |
false | |
end | |
end | |
def ๐ท | |
if @account | |
@account.profile_image = @user.profile_image_url | |
@account.profile_banner = @user.profile_banner_url | |
@account.display_name = @user.name | |
@account.save | |
end | |
end | |
def ๐พ | |
if @account | |
@account.sessions.create({ | |
followers_count: @user.followers_count, | |
favorites_count: @user.favorites_count, | |
friends_count: @user.friends_count | |
}) | |
end | |
end | |
# Will fetch most favorited / retweeted tweets | |
def select_tweets results | |
tweets_podium = [] | |
results.each do |tweet| | |
if !tweet.user_mentions? and tweet.retweet_count > RETWEET_MIN and tweet.favorite_count > FAVORITE_MIN | |
tweets_podium << tweet | |
end | |
# We keep 20 tweets, not more | |
if tweets_podium.length >= 20 | |
return tweets_podium | |
end | |
end | |
# If we are here, we failed in the mission of choosing nice tweets. | |
return tweets_podium | |
end | |
# All begin here | |
def โก๏ธ | |
debug = Rails.env.development? | |
if !debug and @account and @account.is_complete? | |
begin | |
break_friendships | |
make_some_friends | |
make_ever_more_friends | |
say_interesting_things | |
adding_count = (@followed_ids - @followed_cache).length | |
@followed_users.users = @seen_users.uniq | |
rescue Twitter::Error::TooManyRequests => error | |
return | |
end | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment