Skip to content

Instantly share code, notes, and snippets.

@SwooshyCueb
Created November 7, 2014 13:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SwooshyCueb/c5a04b86460ef28f989b to your computer and use it in GitHub Desktop.
Save SwooshyCueb/c5a04b86460ef28f989b to your computer and use it in GitHub Desktop.
DampHitVEVO
#!/usr/bin/env ruby
require 'twitter_ebooks'
require 'yaml'
require 'json'
# Track who we've randomly interacted with globally
$reload_requested = {}
$resync_requested = {}
$bots = []
class GenBot
def initialize(bot, config)
@bot = bot
@model = nil
@conf = config
bot.consumer_key = config["consumer_key"]
bot.consumer_secret = config["consumer_secret"]
modelname = config["username"]
bot.on_startup do
if File.exists?("model/#{modelname}.model")
@model = Ebooks::Model.load("model/#{modelname}.model")
else
fetchtweets(config["sources"])
end
@top100 = @model.keywords.top(100).map(&:to_s).map
@top50 = @model.keywords.top(20).map(&:to_s).map
@have_talked = {}
$reload_requested[modelname] = false
$resync_requested[modelname] = false
#bot.scheduler.join
end
bot.on_message do |dm|
if config["replying"]["dm"]
bot.delay rand(config["response_delay"]["minimum"]..config["response_delay"]["maximum"]) do
bot.reply dm, @model.make_response(dm[:text])
end
end
end
bot.on_follow do |user|
next if config["blacklist"].include?(tweet[:user][:screen_name])
if config["followback"]
bot.follow(user[:screen_name])
end
end
bot.on_mention do |tweet, meta|
# Avoid infinite reply chains (very small chance of crosstalk)
next if tweet[:text].start_with?('RT ') || tweet[:text].start_with?('MT ')
next if tweet[:user][:screen_name].include?(config["bot_strings"]) && rand > 0.05
tokens = Ebooks::NLP.tokenize(tweet[:text])
interesting = tokens.find { |t| @top100.include?(t.downcase) }
very_interesting = tokens.find_all { |t| @top50.include?(t.downcase) }.length > 2
special = tokens.find { |t| config["special_words"].include?(t.downcase) }
if very_interesting
if (config["favoriting"]["mentions"]["supercommon_words"] &&
rand < config["favoriting"]["mentions"]["supercommon_words_frequency"])
favorite(tweet)
end
if (config["retweeting"]["mentions"]["supercommon_words"] &&
rand < config["retweeting"]["mentions"]["supercommon_words_frequency"])
retweet(tweet)
end
if (config["replying"]["mentions"]["supercommon_words"] &&
rand < config["replying"]["mentions"]["supercommon_words_frequency"])
reply(tweet, meta)
end
elsif special
if (config["favoriting"]["mentions"]["match_special"] &&
rand < config["favoriting"]["mentions"]["match_special_frequency"])
favorite(tweet)
end
if (config["retweeting"]["mentions"]["match_special"] &&
rand < config["retweeting"]["mentions"]["match_special_frequency"])
retweet(tweet)
end
if (config["replying"]["mentions"]["match_special"] &&
rand < config["replying"]["mentions"]["match_special_frequency"])
reply(tweet, meta)
end
elsif interesting
if (config["favoriting"]["mentions"]["common_words"] &&
rand < config["favoriting"]["mentions"]["common_words_frequency"])
favorite(tweet)
end
if (config["retweeting"]["mentions"]["common_words"] &&
rand < config["retweeting"]["mentions"]["common_words_frequency"])
retweet(tweet)
end
if (config["replying"]["mentions"]["common_words"] &&
rand < config["replying"]["mentions"]["common_words_frequency"])
reply(tweet, meta)
end
end
end
bot.on_timeline do |tweet, meta|
next if tweet[:retweeted_status] || tweet[:text].start_with?('RT') || tweet[:text].start_with?('MT ')
next if config["blacklist"].include?(tweet[:user][:screen_name])
tokens = Ebooks::NLP.tokenize(tweet[:text])
# We calculate unprompted interaction probability by how well a
# tweet matches our keywords
interesting = tokens.find { |t| @top100.include?(t.downcase) }
very_interesting = tokens.find_all { |t| @top50.include?(t.downcase) }.length > 2
special = tokens.find { |t| config["special_words"].include?(t.downcase) }
if (config["favoriting"]["cold_contact"]["match_special"] && special)
favorite(tweet)
favd = true # Mark this tweet as favorited
#bot.delay rand(config["response_delay"]["minimum"]..config["response_delay"]["maximum"]) do
# bot.follow tweet[:user][:screen_name]
#end
# not sure what we're doing here
# we're looking at tweets in the timeline
# retweets are filtered out, so this is only people we're already following
end
# Any given user will receive at most one random interaction per day
# (barring special cases)
next if @have_talked[tweet[:user][:screen_name]]
@have_talked[tweet[:user][:screen_name]] = true
if very_interesting
if (config["favoriting"]["cold_contact"]["supercommon_words"] &&
rand < config["favoriting"]["cold_contact"]["supercommon_words_frequency"] &&
!favd)
favorite(tweet)
end
if (config["retweeting"]["cold_contact"]["supercommon_words"] &&
rand < config["retweeting"]["cold_contact"]["supercommon_words_frequency"])
retweet(tweet)
end
if (config["replying"]["cold_contact"]["supercommon_words"] &&
rand < config["replying"]["cold_contact"]["supercommon_words_frequency"])
reply(tweet, meta)
end
elsif special
if (config["favoriting"]["cold_contact"]["match_special"] &&
rand < config["favoriting"]["cold_contact"]["match_special_frequency"] &&
!favd)
favorite(tweet)
end
if (config["retweeting"]["cold_contact"]["match_special"] &&
rand < config["retweeting"]["cold_contact"]["match_special_frequency"])
retweet(tweet)
end
if (config["replying"]["cold_contact"]["match_special"] &&
rand < config["replying"]["cold_contact"]["match_special_frequency"])
reply(tweet, meta)
end
elsif interesting
if (config["favoriting"]["cold_contact"]["common_words"] &&
rand < config["favoriting"]["cold_contact"]["common_words_frequency"])
favorite(tweet)
end
if (config["retweeting"]["cold_contact"]["common_words"] &&
rand < config["retweeting"]["cold_contact"]["common_words_frequency"])
retweet(tweet)
end
if (config["replying"]["cold_contact"]["common_words"] &&
rand < config["replying"]["cold_contact"]["common_words_frequency"])
reply(tweet, meta)
end
end
end
bot.scheduler.interval '10s' do
if $reload_requested[@conf["username"]]
bot.log "Configuration reload propagation requested"
config = $cfg[@conf["username"]]
@conf = $cfg[@conf["username"]]
bot.log "Bot configuration reload propagated"
$reload_requested[@conf["username"]] = false
bot.log "Reload propagation request flag reset"
end
if $resync_requested[@conf["username"]]
bot.log "Tweet pool resync requested"
fetchtweets(config["sources"])
bot.log "Tweet pool resync'd"
$resync_requested[@conf["username"]] = false
bot.log "Tweet pool resync request flag reset"
end
end
bot.scheduler.interval config["general_tweet"]["frequency"] do
if config["general_tweet"]["enabled"]
bot.delay rand(0..config["general_tweet"]["frequency_variance"]) do
bot.tweet @model.make_statement
@have_talked = {}
end
end
end
bot.scheduler.interval config["source_refresh"]["interval"] do
if config["source_refresh"]["enabled"]
fetchtweets(config["sources"])
end
end
end
def reply(tweet, meta)
if @conf["replying"]["enabled"]
resp = @model.make_response(meta[:mentionless], meta[:limit])
@bot.delay rand(config["response_delay"]["minimum"]..config["response_delay"]["maximum"]) do
@bot.reply tweet, meta[:reply_prefix] + resp
end
end
end
def favorite(tweet)
if @conf["favoriting"]["enabled"]
@bot.log "Favoriting @#{tweet[:user][:screen_name]}: #{tweet[:text]}"
@bot.delay rand(config["response_delay"]["minimum"]..config["response_delay"]["maximum"]) do
@bot.twitter.favorite(tweet[:id])
end
end
end
def retweet(tweet)
if @conf["retweeting"]["enabled"]
@bot.log "Retweeting @#{tweet[:user][:screen_name]}: #{tweet[:text]}"
@bot.delay rand(config["response_delay"]["minimum"]..config["response_delay"]["maximum"]) do
@bot.twitter.retweet(tweet[:id])
end
end
end
def fetchtweets(usernames)
all_tweets = []
modelname = @conf["username"]
for idx in 0 ... @conf["sources"].size
new_tweets = []
tweets = nil
max_id = nil
username = @conf["sources"][idx]
@bot.log "Fetching tweets from #{username}"
if File.exists?("corpus/#{username}.json")
tweets = JSON.parse(File.read("corpus/#{username}.json"), symbolize_names: true)
else
tweets.nil?
end
opts = {
count: @conf["source_refresh"]["fetch_chunk"],
trim_user: true,
exclude_replies: @conf["source_refresh"]["exclude_replies"],
include_rts: @conf["source_refresh"]["include_rts"]
}
opts[:since_id] = tweets[0][:id] unless tweets.nil?
loop do
opts[:max_id] = max_id unless max_id.nil?
new = @bot.twitter.user_timeline(username, opts)
break if new.length <= 1
new_tweets += new
@bot.log "Received #{new_tweets.length} new tweets"
max_id = new.last.id
end
if new_tweets.length == 0
@bot.log "No new tweets"
else
tweets ||= []
tweets = new_tweets.map(&:attrs).each { |tw|
tw.delete(:entities)
} + tweets
File.open("corpus/#{username}.json", 'w+') do |f|
f.write(JSON.pretty_generate(tweets))
end
end
all_tweets = tweets + all_tweets
all_tweets.sort! { |a,b| a["id"] <=> b["id"] }
end
if all_tweets.length == 0
@bot.log "No new tweets from all sources"
else
File.open("corpus/#{modelname}_all.json", 'w+') do |f|
f.write(JSON.pretty_generate(all_tweets))
end
@model = Ebooks::Model.consume("corpus/#{modelname}_all.json")
@bot.log "Writing consumed corpus"
@model.save("model/#{modelname}.model")
end
end
end
if $d_cfg["daemon"]["daemonize"]
puts "Daemonizing..."
Process.daemon(true,false)
$stdout.reopen($d_cfg["daemon"]["log_file"], "a+")
$stdout.sync = true
$stderr.reopen($d_cfg["daemon"]["log_file"], "a+")
$stderr.sync = true
end
File.open($d_cfg["daemon"]["pid_file"], "w") do |fp|
fp.write(Process.pid)
end
def make_bot(bot, config)
newbot = GenBot.new(bot, config)
$bots = $bots.to_a.push(newbot)
end
$cfg = YAML.load_file('config.yaml')
Signal.trap("HUP") do
puts "HUP signal recieved, reloading configuration."
$cfg = YAML.load_file('config.yaml')
puts "Setting configuration reload propagation request flags."
for idx in 0 ... $cfg["bots"].size
$reload_requested[$cfg["bots"][idx]] = true
end
end
Signal.trap("USR1") do
puts "USR1 signal recieved, seting tweet pool resync request flags."
for idx in 0 ... $cfg["bots"].size
$resync_requested[$cfg["bots"][idx]] = true
end
end
for idx in 0 ... $cfg["bots"].size
Ebooks::Bot.new($cfg["bots"][idx]) do |bot|
botcfg = $cfg[$cfg["bots"][idx]]
bot.oauth_token = botcfg["oauth_token"]
bot.oauth_token_secret = botcfg["oauth_secret"]
make_bot(bot, botcfg)
end
end
bots: [DampHitVEVO]
defaults: &defaults
consumer_key:
consumer_secret:
bot_strings: ebooks
sources: [SwooshyCueb]
blacklist: []
special_words: [wolfbirb, outragehiatus, odst]
favoriting: &favoriting
enabled: true
cold_contact:
common_words: true
common_words_frequency: 0.5
supercommon_words: true
supercommon_words_frequency: 0.5
match_special: true
match_special_frequency: 0.1
mentions:
common_words: false
common_words_frequency: 0
supercommon_words: true
supercommon_words_frequency: 0.6
match_special: true
match_special_frequency: 1
replying: &replying
enabled: true
cold_contact:
common_words: true
common_words_frequency: 0.1
supercommon_words: true
supercommon_words_frequency: 0.5
match_special: true
match_special_frequency: 0.5
mentions:
common_words: true
common_words_frequency: 1
supercommon_words: true
supercommon_words_frequency: 1
match_special: true
match_special_frequency: 1
dm: true
retweeting: &retweeting
enabled: true
cold_contact:
common_words: false
common_words_frequency: 0
supercommon_words: true
supercommon_words_frequency: 0.1
match_special: true
match_special_frequency: 0.1
mentions:
common_words: false
common_words_frequency: 0
supercommon_words: true
supercommon_words_frequency: 0.1
match_special: true
match_special_frequency: 0.1
followback: true
general_tweet: &general_tweet
enabled: true
frequency: 3h
frequency_variance: 3600
source_refresh: &source_refresh
enabled: true
interval: 6h
fetch_chunk: 200
include_rts: false
exclude_replies: false
response_delay: &response_delay
minimum: 3
maximum: 32
daemon:
daemonize: true
pid_file: /opt/ebooks/ebooks.pid
log_file: /opt/ebooks/ebooks.log
timeout: 120
DampHitVEVO:
<<: *defaults
username: DampHitVEVO
oauth_token:
oauth_secret:
sources: [ForgedScarecrow, SwooshyCueb, DampHit, pkmnvietnam_bot]
general_tweet:
<<: *general_tweet
frequency: 4298s
frequency_variance: 15451
favoriting:
<<: *favoriting
enabled: true
mentions:
common_words: false
common_words_frequency: 0
supercommon_words: true
supercommon_words_frequency: 0.6
match_special: true
match_special_frequency: 1
retweeting:
<<: *retweeting
enabled: true
#!/usr/bin/env ruby
require 'yaml'
$d_cfg = YAML.load_file('config.yaml')
def d_stop()
puts "Stopping ebooks daemon..."
pidwait = 0
if File.exists?($d_cfg["daemon"]["pid_file"])
begin
loop do
Process.kill(15, File.read($d_cfg["daemon"]["pid_file"]).to_i)
break if pidwait == $d_cfg["daemon"]["timeout"]
sleep 1
pidwait = 1 + pidwait
end
rescue Errno::ESRCH
if pidwait == 0
puts "Daemon is not running."
File.delete($d_cfg["daemon"]["pid_file"])
exit(false)
else
puts "Ebooks daemon stopped."
File.delete($d_cfg["daemon"]["pid_file"])
end
else
if pidwait == $d_cfg["daemon"]["timeout"]
puts "Could not stop daemon."
exit(false)
else
puts "Internal error."
exit(false)
end
end
else
puts "Daemon is not running."
exit(false)
end
end
def d_kill(dies)
puts "Killing ebooks daemon..."
pidwait = 0
if File.exists?($d_cfg["daemon"]["pid_file"])
begin
loop do
Process.kill(9, File.read($d_cfg["daemon"]["pid_file"]).to_i)
break if pidwait == $d_cfg["daemon"]["timeout"]
sleep 1
pidwait = 1 + pidwait
end
rescue Errno::ESRCH
if pidwait == 0
puts "Daemon is not running."
File.delete($d_cfg["daemon"]["pid_file"])
exit(false) if dies
else
puts "Ebooks daemon killed."
File.delete($d_cfg["daemon"]["pid_file"])
exit(true) if dies
end
else
if pidwait == $d_cfg["daemon"]["timeout"]
puts "Could not kill daemon."
exit(false)
else
puts "Internal error."
exit(false)
end
end
else
puts "Daemon is not running."
exit(false) if dies
end
end
def d_usage(exitstatus)
puts "Usage: ebooksd {start|stop|restart|reload|force-stop|force-restart|resync|status}"
exit(exitstatus)
end
def d_start()
puts "Starting ebooks..."
require_relative 'bots'
EM.run do
Ebooks::Bot.all.each do |bot|
bot.start
end
end
end
if ARGV[0]
case ARGV[0]
when 'stop'
d_stop()
exit(true)
when 'kill', 'force-stop'
d_kill(true)
when 'reload'
puts "Reloading ebooks configuration..."
if File.exists?($d_cfg["daemon"]["pid_file"])
begin
Process.kill(1, File.read($d_cfg["daemon"]["pid_file"]).to_i)
rescue Errno::ESRCH
puts "Daemon is not running."
File.delete($d_cfg["daemon"]["pid_file"])
exit(false)
end
exit(true)
else
puts "Daemon is not running."
exit(false)
end
when 'resync'
puts "Refreshing ebooks cached tweets..."
if File.exists?($d_cfg["daemon"]["pid_file"])
begin
Process.kill(10, File.read($d_cfg["daemon"]["pid_file"]).to_i)
rescue Errno::ESRCH
puts "Daemon is not running."
File.delete($d_cfg["daemon"]["pid_file"])
exit(false)
end
exit(true)
else
puts "Daemon is not running."
exit(false)
end
when 'restart'
d_stop()
d_start()
when 'force-restart'
d_kill(false)
d_start()
when 'start'
puts "Starting ebooks daemon..."
if File.exists?($d_cfg["daemon"]["pid_file"])
begin
Process.kill(0, File.read($d_cfg["daemon"]["pid_file"]).to_i)
rescue Errno::ESRCH
File.delete($d_cfg["daemon"]["pid_file"])
else
puts "Daemon already running."
exit(false)
end
end
d_start()
when 'status'
if File.exists?($d_cfg["daemon"]["pid_file"])
begin
Process.kill(0, File.read($d_cfg["daemon"]["pid_file"]).to_i)
rescue Errno::ESRCH
puts "Daemon is not running."
File.delete($d_cfg["daemon"]["pid_file"])
exit(true)
end
puts "Daemon is running"
exit(true)
else
puts "Daemon is not running."
exit(true)
end
when 'help'
d_usage(true)
else
d_usage(false)
end
else
d_usage(false)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment