mirror of
https://github.com/thewesker/ebooks_example.git
synced 2025-12-20 04:11:13 -05:00
3.0.0 update
This commit is contained in:
2
Gemfile
2
Gemfile
@@ -1,4 +1,4 @@
|
|||||||
source 'http://rubygems.org'
|
source 'http://rubygems.org'
|
||||||
ruby '1.9.3'
|
ruby '2.1.3'
|
||||||
|
|
||||||
gem 'twitter_ebooks'
|
gem 'twitter_ebooks'
|
||||||
|
|||||||
72
Gemfile.lock
Normal file
72
Gemfile.lock
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
GEM
|
||||||
|
remote: http://rubygems.org/
|
||||||
|
specs:
|
||||||
|
addressable (2.3.6)
|
||||||
|
awesome_print (1.2.0)
|
||||||
|
bloomfilter-rb (2.1.1)
|
||||||
|
redis
|
||||||
|
buftok (0.2.0)
|
||||||
|
coderay (1.1.0)
|
||||||
|
engtagger (0.2.0)
|
||||||
|
equalizer (0.0.9)
|
||||||
|
eventmachine (1.0.3)
|
||||||
|
faraday (0.9.0)
|
||||||
|
multipart-post (>= 1.2, < 3)
|
||||||
|
fast-stemmer (1.0.2)
|
||||||
|
gingerice (1.2.2)
|
||||||
|
addressable
|
||||||
|
awesome_print
|
||||||
|
highscore (1.2.0)
|
||||||
|
bloomfilter-rb (>= 2.1.1)
|
||||||
|
whatlanguage (>= 1.0.0)
|
||||||
|
htmlentities (4.3.2)
|
||||||
|
http (0.6.3)
|
||||||
|
http_parser.rb (~> 0.6.0)
|
||||||
|
http_parser.rb (0.6.0)
|
||||||
|
json (1.8.1)
|
||||||
|
memoizable (0.4.2)
|
||||||
|
thread_safe (~> 0.3, >= 0.3.1)
|
||||||
|
method_source (0.8.2)
|
||||||
|
multipart-post (2.0.0)
|
||||||
|
naught (1.0.0)
|
||||||
|
pry (0.10.1)
|
||||||
|
coderay (~> 1.1.0)
|
||||||
|
method_source (~> 0.8.1)
|
||||||
|
slop (~> 3.4)
|
||||||
|
redis (3.1.0)
|
||||||
|
rufus-scheduler (3.0.9)
|
||||||
|
tzinfo
|
||||||
|
simple_oauth (0.3.0)
|
||||||
|
slop (3.6.0)
|
||||||
|
thread_safe (0.3.4)
|
||||||
|
twitter (5.13.0)
|
||||||
|
addressable (~> 2.3)
|
||||||
|
buftok (~> 0.2.0)
|
||||||
|
equalizer (~> 0.0.9)
|
||||||
|
faraday (~> 0.9.0)
|
||||||
|
http (~> 0.6.0)
|
||||||
|
http_parser.rb (~> 0.6.0)
|
||||||
|
json (~> 1.8)
|
||||||
|
memoizable (~> 0.4.0)
|
||||||
|
naught (~> 1.0)
|
||||||
|
simple_oauth (~> 0.3.0)
|
||||||
|
twitter_ebooks (3.0.0)
|
||||||
|
engtagger
|
||||||
|
eventmachine (~> 1.0.3)
|
||||||
|
fast-stemmer
|
||||||
|
gingerice
|
||||||
|
highscore
|
||||||
|
htmlentities
|
||||||
|
pry
|
||||||
|
rufus-scheduler
|
||||||
|
simple_oauth
|
||||||
|
twitter (~> 5.0)
|
||||||
|
tzinfo (1.2.2)
|
||||||
|
thread_safe (~> 0.1)
|
||||||
|
whatlanguage (1.0.5)
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
ruby
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
twitter_ebooks
|
||||||
@@ -14,6 +14,6 @@ ebooks consume corpus/username.json
|
|||||||
|
|
||||||
Populate bots.rb with your auth details, the bot username and model name, then:
|
Populate bots.rb with your auth details, the bot username and model name, then:
|
||||||
|
|
||||||
`./run.rb`
|
`ebooks start`
|
||||||
|
|
||||||
Also runs as a Heroku app! See the [twitter_ebooks](https://github.com/mispy/twitter_ebooks) README for more information.
|
Also runs as a Heroku app! See the [twitter_ebooks](https://github.com/mispy/twitter_ebooks) README for more information.
|
||||||
|
|||||||
196
bots.rb
196
bots.rb
@@ -1,138 +1,134 @@
|
|||||||
#!/usr/bin/env ruby
|
|
||||||
|
|
||||||
require 'twitter_ebooks'
|
require 'twitter_ebooks'
|
||||||
include Ebooks
|
|
||||||
|
|
||||||
CONSUMER_KEY = ""
|
# Information about a particular Twitter user we know
|
||||||
CONSUMER_SECRET = ""
|
class UserInfo
|
||||||
OATH_TOKEN = "" # oauth token for ebooks account
|
attr_reader :username
|
||||||
OAUTH_TOKEN_SECRET = "" # oauth secret for ebooks account
|
|
||||||
|
|
||||||
ROBOT_ID = "ebooks" # Avoid infinite reply chains
|
# @return [Integer] how many times we can pester this user unprompted
|
||||||
TWITTER_USERNAME = "ebooks_username" # Ebooks account username
|
attr_accessor :pesters_left
|
||||||
TEXT_MODEL_NAME = "username" # This should be the name of the text model
|
|
||||||
|
|
||||||
DELAY = 2..30 # Simulated human reply delay range in seconds
|
# @param username [String]
|
||||||
BLACKLIST = ['insomnius', 'upulie'] # Grumpy users to avoid interaction with
|
def initialize(username)
|
||||||
SPECIAL_WORDS = ['ebooks', 'bot', 'bots', 'clone', 'singularity', 'world domination']
|
@username = username
|
||||||
|
@pesters_left = 1
|
||||||
# Track who we've randomly interacted with globally
|
|
||||||
$have_talked = {}
|
|
||||||
|
|
||||||
class GenBot
|
|
||||||
def initialize(bot, modelname)
|
|
||||||
@bot = bot
|
|
||||||
@model = nil
|
|
||||||
|
|
||||||
bot.consumer_key = CONSUMER_KEY
|
|
||||||
bot.consumer_secret = CONSUMER_SECRET
|
|
||||||
|
|
||||||
bot.on_startup do
|
|
||||||
@model = Model.load("model/#{modelname}.model")
|
|
||||||
@top100 = @model.keywords.top(100).map(&:to_s).map(&:downcase)
|
|
||||||
@top50 = @model.keywords.top(20).map(&:to_s).map(&:downcase)
|
|
||||||
end
|
|
||||||
|
|
||||||
bot.on_message do |dm|
|
|
||||||
bot.delay DELAY do
|
|
||||||
bot.reply dm, @model.make_response(dm[:text])
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
bot.on_follow do |user|
|
class CloneBot < Ebooks::Bot
|
||||||
bot.delay DELAY do
|
attr_accessor :original, :model_path
|
||||||
bot.follow user[:screen_name]
|
|
||||||
|
def configure
|
||||||
|
# Configuration for all CloneBots
|
||||||
|
self.consumer_key = ""
|
||||||
|
self.consumer_secret = ""
|
||||||
|
self.blacklist = ['kylelehk', 'friedrichsays', 'Sudieofna', 'tnietzschequote', 'NerdsOnPeriod', 'FSR', 'BafflingQuotes', 'Obey_Nxme']
|
||||||
|
|
||||||
|
@userinfo = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def model
|
||||||
|
@model_path ||= "model/#{original}.model"
|
||||||
|
if @model.nil?
|
||||||
|
log "Loading model #{model_path}"
|
||||||
|
@model = Ebooks::Model.load(model_path)
|
||||||
|
else
|
||||||
|
@model
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
bot.on_mention do |tweet, meta|
|
def top100; @top100 ||= model.keywords.take(100); end
|
||||||
# Avoid infinite reply chains (very small chance of crosstalk)
|
def top20; @top20 ||= model.keywords.take(20); end
|
||||||
next if tweet[:user][:screen_name].include?(ROBOT_ID) && rand > 0.05
|
|
||||||
|
|
||||||
tokens = NLP.tokenize(tweet[:text])
|
def delay(&b)
|
||||||
|
sleep (1..4).to_a.sample
|
||||||
very_interesting = tokens.find_all { |t| @top50.include?(t.downcase) }.length > 2
|
b.call
|
||||||
special = tokens.find { |t| SPECIAL_WORDS.include?(t) }
|
|
||||||
|
|
||||||
if very_interesting || special
|
|
||||||
favorite(tweet)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
reply(tweet, meta)
|
def on_startup
|
||||||
end
|
model
|
||||||
|
|
||||||
bot.on_timeline do |tweet, meta|
|
scheduler.cron '0 0 * * *' do
|
||||||
next if tweet[:retweeted_status] || tweet[:text].start_with?('RT')
|
# Each day at midnight, post a single tweet
|
||||||
next if BLACKLIST.include?(tweet[:user][:screen_name])
|
tweet model.make_statement
|
||||||
|
|
||||||
tokens = 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| SPECIAL_WORDS.include?(t) }
|
|
||||||
|
|
||||||
if special
|
|
||||||
favorite(tweet)
|
|
||||||
favd = true # Mark this tweet as favorited
|
|
||||||
|
|
||||||
bot.delay DELAY do
|
|
||||||
bot.follow tweet[:user][:screen_name]
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Any given user will receive at most one random interaction per day
|
def on_direct_message(dm)
|
||||||
# (barring special cases)
|
delay do
|
||||||
next if $have_talked[tweet[:user][:screen_name]]
|
reply dm, model.make_response(dm.text)
|
||||||
$have_talked[tweet[:user][:screen_name]] = true
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if very_interesting || special
|
def on_mention(tweet)
|
||||||
favorite(tweet) if (rand < 0.5 && !favd) # Don't fav the tweet if we did earlier
|
# Become more inclined to pester a user when they talk to us
|
||||||
|
userinfo(tweet.user.screen_name).pesters_left += 1
|
||||||
|
delay do
|
||||||
|
reply(tweet, model.make_response(meta(tweet).mentionless, meta(tweet).limit))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def on_timeline(tweet)
|
||||||
|
return if tweet.retweeted_status?
|
||||||
|
tokens = Ebooks::NLP.tokenize(tweet.text)
|
||||||
|
|
||||||
|
interesting = tokens.find { |t| top100.include?(t.downcase) }
|
||||||
|
very_interesting = tokens.find_all { |t| top20.include?(t.downcase) }.length > 2
|
||||||
|
|
||||||
|
return unless can_pester?(tweet.user.screen_name)
|
||||||
|
|
||||||
|
delay do
|
||||||
|
if very_interesting
|
||||||
|
favorite(tweet) if rand < 0.5
|
||||||
retweet(tweet) if rand < 0.1
|
retweet(tweet) if rand < 0.1
|
||||||
reply(tweet, meta) if rand < 0.1
|
reply(tweet, model.make_response(meta(tweet).mentionless, meta(tweet).limit)) if rand < 0.05
|
||||||
elsif interesting
|
elsif interesting
|
||||||
favorite(tweet) if rand < 0.1
|
favorite(tweet) if rand < 0.05
|
||||||
reply(tweet, meta) if rand < 0.05
|
reply(tweet, model.make_response(meta(tweet).mentionless, meta(tweet).limit)) if rand < 0.01
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Schedule a main tweet for every day at midnight
|
# Find information we've collected about a user
|
||||||
bot.scheduler.cron '0 0 * * *' do
|
# @param username [String]
|
||||||
bot.tweet @model.make_statement
|
# @return [Ebooks::UserInfo]
|
||||||
$have_talked = {}
|
def userinfo(username)
|
||||||
end
|
@userinfo[username] ||= UserInfo.new(username)
|
||||||
end
|
end
|
||||||
|
|
||||||
def reply(tweet, meta)
|
# Check if we're allowed to send unprompted tweets to a user
|
||||||
resp = @model.make_response(meta[:mentionless], meta[:limit])
|
# @param username [String]
|
||||||
@bot.delay DELAY do
|
# @return [Boolean]
|
||||||
@bot.reply tweet, meta[:reply_prefix] + resp
|
def can_pester?(username)
|
||||||
|
userinfo(username).pesters_left > 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Only follow our original user or people who are following our original user
|
||||||
|
# @param user [Twitter::User]
|
||||||
|
def can_follow?(username)
|
||||||
|
@original.nil? || username == @original || twitter.friendship?(username, @original)
|
||||||
end
|
end
|
||||||
|
|
||||||
def favorite(tweet)
|
def favorite(tweet)
|
||||||
@bot.log "Favoriting @#{tweet[:user][:screen_name]}: #{tweet[:text]}"
|
if !can_follow?(tweet.user.screen_name)
|
||||||
@bot.delay DELAY do
|
log "Unfollowing @#{tweet.user.screen_name}"
|
||||||
@bot.twitter.favorite(tweet[:id])
|
twitter.unfollow(tweet.user.screen_name)
|
||||||
|
else
|
||||||
|
super(tweet)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def retweet(tweet)
|
def on_follow(user)
|
||||||
@bot.log "Retweeting @#{tweet[:user][:screen_name]}: #{tweet[:text]}"
|
if can_follow?(user.screen_name)
|
||||||
@bot.delay DELAY do
|
follow(user.screen_name)
|
||||||
@bot.twitter.retweet(tweet[:id])
|
else
|
||||||
|
log "Not following @#{user.screen_name}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_bot(bot, modelname)
|
CloneBot.new("abby_ebooks") do |bot|
|
||||||
GenBot.new(bot, modelname)
|
bot.access_token = ""
|
||||||
end
|
bot.access_token_secret = ""
|
||||||
|
|
||||||
Ebooks::Bot.new(TWITTER_USERNAME) do |bot|
|
bot.original = "0xabad1dea"
|
||||||
bot.oauth_token = OATH_TOKEN
|
|
||||||
bot.oauth_token_secret = OAUTH_TOKEN_SECRET
|
|
||||||
|
|
||||||
make_bot(bot, TEXT_MODEL_NAME)
|
|
||||||
end
|
end
|
||||||
|
|||||||
9413
model/0xabad1dea.model
Normal file
9413
model/0xabad1dea.model
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user