Created
November 7, 2014 13:08
-
-
Save SwooshyCueb/c5a04b86460ef28f989b to your computer and use it in GitHub Desktop.
DampHitVEVO
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
#!/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 |
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
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 |
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
#!/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