More cleanup
This commit is contained in:
		
							parent
							
								
									1977445b1c
								
							
						
					
					
						commit
						822f5e4c6c
					
				
					 13 changed files with 144 additions and 14884 deletions
				
			
		
							
								
								
									
										72
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										72
									
								
								README.md
									
										
									
									
									
								
							|  | @ -8,8 +8,11 @@ A framework for building interactive twitterbots which respond to mentions/DMs. | |||
| 
 | ||||
| ## New in 3.0 | ||||
| 
 | ||||
| - Bots now run in their own threads (no eventmachine), and startup is parallelized | ||||
| - Replies are slightly rate-limited by default to prevent infinite bot convos | ||||
| - Bots run in their own threads (no eventmachine), and startup is parallelized | ||||
| - Bots start with `ebooks start`, and no longer die on unhandled exceptions | ||||
| - `ebooks auth` command will create new access tokens, for running multiple bots | ||||
| - `ebooks console` starts a ruby interpreter with bots loaded (see Ebooks::Bot.all) | ||||
| - Replies are slightly rate-limited to prevent infinite bot convos | ||||
| - Non-participating users in a mention chain will be dropped after a few tweets | ||||
| 
 | ||||
| ## Installation | ||||
|  | @ -26,47 +29,57 @@ Run `ebooks new <reponame>` to generate a new repository containing a sample bot | |||
| 
 | ||||
| ``` ruby | ||||
| # This is an example bot definition with event handlers commented out | ||||
| # You can define as many of these as you like; they will run simultaneously | ||||
| # You can define and instantiate as many bots as you like | ||||
| 
 | ||||
| Ebooks::Bot.new("abby_ebooks") do |bot| | ||||
|   # Consumer details come from registering an app at https://dev.twitter.com/ | ||||
|   # OAuth details can be fetched with https://github.com/marcel/twurl | ||||
|   bot.consumer_key = "" # Your app consumer key | ||||
|   bot.consumer_secret = "" # Your app consumer secret | ||||
|   bot.oauth_token = "" # Token connecting the app to this account | ||||
|   bot.oauth_token_secret = "" # Secret connecting the app to this account | ||||
| class MyBot < Ebooks::Bot | ||||
|   # Configuration here applies to all MyBots | ||||
|   def configure | ||||
|     # Consumer details come from registering an app at https://dev.twitter.com/ | ||||
|     # Once you have consumer details, use "ebooks auth" for new access tokens | ||||
|     self.consumer_key = '' # Your app consumer key | ||||
|     self.consumer_secret = '' # Your app consumer secret | ||||
| 
 | ||||
|   bot.on_startup do | ||||
|     # Run some startup task | ||||
|     # puts "I'm ready!" | ||||
|     # Users to block instead of interacting with | ||||
|     self.blacklist = ['tnietzschequote'] | ||||
| 
 | ||||
|     # Range in seconds to randomize delay when bot.delay is called | ||||
|     self.delay_range = 1..6 | ||||
|   end | ||||
| 
 | ||||
|   bot.on_message do |dm| | ||||
|   def on_startup | ||||
|     scheduler.every '24h' do | ||||
|       # Tweet something every 24 hours | ||||
|       # See https://github.com/jmettraux/rufus-scheduler | ||||
|       # bot.tweet("hi") | ||||
|       # bot.pictweet("hi", "cuteselfie.jpg") | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def on_message(dm) | ||||
|     # Reply to a DM | ||||
|     # bot.reply(dm, "secret secrets") | ||||
|   end | ||||
| 
 | ||||
|   bot.on_follow do |user| | ||||
|   def on_follow(user) | ||||
|     # Follow a user back | ||||
|     # bot.follow(user[:screen_name]) | ||||
|   end | ||||
| 
 | ||||
|   bot.on_mention do |tweet, meta| | ||||
|   def on_mention(tweet) | ||||
|     # Reply to a mention | ||||
|     # bot.reply(tweet, meta[:reply_prefix] + "oh hullo") | ||||
|     # bot.reply(tweet, meta(tweet)[:reply_prefix] + "oh hullo") | ||||
|   end | ||||
| 
 | ||||
|   bot.on_timeline do |tweet, meta| | ||||
|   def on_timeline(tweet) | ||||
|     # Reply to a tweet in the bot's timeline | ||||
|     # bot.reply(tweet, meta[:reply_prefix] + "nice tweet") | ||||
|     # bot.reply(tweet, meta(tweet)[:reply_prefix] + "nice tweet") | ||||
|   end | ||||
| end | ||||
| 
 | ||||
|   bot.scheduler.every '24h' do | ||||
|     # Tweet something every 24 hours | ||||
|     # See https://github.com/jmettraux/rufus-scheduler | ||||
|     # bot.tweet("hi") | ||||
| 	# bot.pictweet("hi", "cuteselfie.jpg", ":possibly_sensitive => true") | ||||
|   end | ||||
| # Make a MyBot and attach it to an account | ||||
| MyBot.new("{{BOT_NAME}}") do |bot| | ||||
|   bot.access_token = "" # Token connecting the app to this account | ||||
|   bot.access_token_secret = "" # Secret connecting the app to this account | ||||
| end | ||||
| ``` | ||||
| 
 | ||||
|  | @ -107,7 +120,6 @@ Text files use newlines and full stops to seperate statements. | |||
| Once you have a model, the primary use is to produce statements and related responses to input, using a pseudo-Markov generator: | ||||
| 
 | ||||
| ``` ruby | ||||
| > require 'twitter_ebooks' | ||||
| > model = Ebooks::Model.load("model/0xabad1dea.model") | ||||
| > model.make_statement(140) | ||||
| => "My Terrible Netbook may be the kind of person who buys Starbucks, but this Rackspace vuln is pretty straight up a backdoor" | ||||
|  | @ -118,14 +130,18 @@ Once you have a model, the primary use is to produce statements and related resp | |||
| The secondary function is the "interesting keywords" list. For example, I use this to determine whether a bot wants to fav/retweet/reply to something in its timeline: | ||||
| 
 | ||||
| ``` ruby | ||||
| top100 = model.keywords.top(100) | ||||
| top100 = model.keywords.take(100) | ||||
| tokens = Ebooks::NLP.tokenize(tweet[:text]) | ||||
| 
 | ||||
| if tokens.find { |t| top100.include?(t) } | ||||
|   bot.twitter.favorite(tweet[:id]) | ||||
|   bot.favorite(tweet[:id]) | ||||
| end | ||||
| ``` | ||||
| 
 | ||||
| ## Bot niceness | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| ## Other notes | ||||
| 
 | ||||
| If you're using Heroku, which has no persistent filesystem, automating the process of archiving, consuming and updating can be tricky. My current solution is just a daily cron job which commits and pushes for me, which is pretty hacky. | ||||
|  |  | |||
							
								
								
									
										74
									
								
								bin/ebooks
									
										
									
									
									
								
							
							
						
						
									
										74
									
								
								bin/ebooks
									
										
									
									
									
								
							|  | @ -4,6 +4,12 @@ | |||
| require 'twitter_ebooks' | ||||
| require 'ostruct' | ||||
| 
 | ||||
| module Ebooks::Util | ||||
|   def pretty_exception(e) | ||||
| 
 | ||||
|   end | ||||
| end | ||||
| 
 | ||||
| module Ebooks::CLI | ||||
|   APP_PATH = Dir.pwd # XXX do some recursive thing instead | ||||
|   HELP = OpenStruct.new | ||||
|  | @ -17,8 +23,7 @@ Usage: | |||
|      ebooks consume <corpus_path> [corpus_path2] [...] | ||||
|      ebooks consume-all <corpus_path> [corpus_path2] [...] | ||||
|      ebooks gen <model_path> [input] | ||||
|      ebooks score <model_path> <input> | ||||
|      ebooks archive <username> <outpath> | ||||
|      ebooks archive <username> [path] | ||||
|      ebooks tweet <model_path> <botname> | ||||
| STR | ||||
| 
 | ||||
|  | @ -50,13 +55,18 @@ STR | |||
|       exit 1 | ||||
|     end | ||||
| 
 | ||||
|     FileUtils.cp_r(SKELETON_PATH, path) | ||||
|     FileUtils.cp_r(Ebooks::SKELETON_PATH, path) | ||||
| 
 | ||||
|     File.open(File.join(path, 'bots.rb'), 'w') do |f| | ||||
|       template = File.read(File.join(SKELETON_PATH, 'bots.rb')) | ||||
|       template = File.read(File.join(Ebooks::SKELETON_PATH, 'bots.rb')) | ||||
|       f.write(template.gsub("{{BOT_NAME}}", reponame)) | ||||
|     end | ||||
| 
 | ||||
|     File.open(File.join(path, 'Gemfile'), 'w') do |f| | ||||
|       template = File.read(File.join(Ebooks::SKELETON_PATH, 'Gemfile')) | ||||
|       f.write(template.gsub("{{RUBY_VERSION}}", RUBY_VERSION)) | ||||
|     end | ||||
| 
 | ||||
|     log "New twitter_ebooks app created at #{reponame}" | ||||
|   end | ||||
| 
 | ||||
|  | @ -78,7 +88,7 @@ STR | |||
|       shortname = filename.split('.')[0..-2].join('.') | ||||
| 
 | ||||
|       outpath = File.join(APP_PATH, 'model', "#{shortname}.model") | ||||
|       Model.consume(path).save(outpath) | ||||
|       Ebooks::Model.consume(path).save(outpath) | ||||
|       log "Corpus consumed to #{outpath}" | ||||
|     end | ||||
|   end | ||||
|  | @ -97,15 +107,7 @@ STR | |||
|     end | ||||
| 
 | ||||
|     outpath = File.join(APP_PATH, 'model', "#{name}.model") | ||||
|     #pathes.each do |path| | ||||
|     #  filename = File.basename(path) | ||||
|     #  shortname = filename.split('.')[0..-2].join('.') | ||||
|     # | ||||
|     #  outpath = File.join(APP_PATH, 'model', "#{shortname}.model") | ||||
|     #  Model.consume(path).save(outpath) | ||||
|     #  log "Corpus consumed to #{outpath}" | ||||
|     #end | ||||
|     Model.consume_all(paths).save(outpath) | ||||
|     Ebooks::Model.consume_all(paths).save(outpath) | ||||
|     log "Corpuses consumed to #{outpath}" | ||||
|   end | ||||
| 
 | ||||
|  | @ -122,7 +124,7 @@ STR | |||
|       exit 1 | ||||
|     end | ||||
| 
 | ||||
|     model = Model.load(model_path) | ||||
|     model = Ebooks::Model.load(model_path) | ||||
|     if input && !input.empty? | ||||
|       puts "@cmd " + model.make_response(input, 135) | ||||
|     else | ||||
|  | @ -130,38 +132,22 @@ STR | |||
|     end | ||||
|   end | ||||
| 
 | ||||
|   HELP.score = <<-STR | ||||
|     Usage: ebooks score <model_path> <input> | ||||
| 
 | ||||
|     Scores "interest" in some text input according to how | ||||
|     well unique keywords match the model. | ||||
|   STR | ||||
| 
 | ||||
|   def self.score(model_path, input) | ||||
|     if model_path.nil? || input.nil? | ||||
|       help :score | ||||
|       exit 1 | ||||
|     end | ||||
| 
 | ||||
|     model = Model.load(model_path) | ||||
|     model.score_interest(input) | ||||
|   end | ||||
| 
 | ||||
|   HELP.archive = <<-STR | ||||
|     Usage: ebooks archive <username> <outpath> | ||||
|     Usage: ebooks archive <username> [outpath] | ||||
| 
 | ||||
|     Downloads a json corpus of the <username>'s tweets to <outpath>. | ||||
|     Downloads a json corpus of the <username>'s tweets. | ||||
|     Output defaults to corpus/<username>.json | ||||
|     Due to API limitations, this can only receive up to ~3000 tweets | ||||
|     into the past. | ||||
|   STR | ||||
| 
 | ||||
|   def self.archive(username, outpath) | ||||
|     if username.nil? || outpath.nil? | ||||
|   def self.archive(username, outpath=nil) | ||||
|     if username.nil? | ||||
|       help :archive | ||||
|       exit 1 | ||||
|     end | ||||
| 
 | ||||
|     Archive.new(username, outpath).sync | ||||
|     Ebooks::Archive.new(username, outpath).sync | ||||
|   end | ||||
| 
 | ||||
|   HELP.tweet = <<-STR | ||||
|  | @ -178,10 +164,9 @@ STR | |||
|     end | ||||
| 
 | ||||
|     load File.join(APP_PATH, 'bots.rb') | ||||
|     model = Model.load(modelpath) | ||||
|     model = Ebooks::Model.load(modelpath) | ||||
|     statement = model.make_statement | ||||
|     log "@#{botname}: #{statement}" | ||||
|     bot = Bot.get(botname) | ||||
|     bot = Ebooks::Bot.get(botname) | ||||
|     bot.configure | ||||
|     bot.tweet(statement) | ||||
|   end | ||||
|  | @ -223,7 +208,7 @@ STR | |||
| 
 | ||||
|     access_token = request_token.get_access_token(oauth_verifier: pin) | ||||
| 
 | ||||
|     log "Account authorized successfully.\n" + | ||||
|     log "Account authorized successfully. Make sure to put these in your bots.rb!\n" + | ||||
|          "  access token: #{access_token.token}\n" + | ||||
|          "  access token secret: #{access_token.secret}" | ||||
|   end | ||||
|  | @ -271,9 +256,9 @@ STR | |||
|         loop do | ||||
|           begin | ||||
|             bot.start | ||||
|           rescue Exception | ||||
|             bot.log $! | ||||
|             puts $@ | ||||
|           rescue Exception => e | ||||
|             bot.log e.inspect | ||||
|             puts e.backtrace.map { |s| "\t"+s }.join("\n") | ||||
|           end | ||||
|           bot.log "Sleeping before reconnect" | ||||
|           sleep 5 | ||||
|  | @ -334,7 +319,6 @@ STR | |||
|     when "consume" then consume(args[1..-1]) | ||||
|     when "consume-all" then consume_all(args[1], args[2..-1]) | ||||
|     when "gen" then gen(args[1], args[2..-1].join(' ')) | ||||
|     when "score" then score(args[1], args[2..-1].join(' ')) | ||||
|     when "archive" then archive(args[1], args[2]) | ||||
|     when "tweet" then tweet(args[1], args[2]) | ||||
|     when "jsonify" then jsonify(args[1..-1]) | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ module Ebooks | |||
|   SKELETON_PATH = File.join(GEM_PATH, 'skeleton') | ||||
|   TEST_PATH = File.join(GEM_PATH, 'test') | ||||
|   TEST_CORPUS_PATH = File.join(TEST_PATH, 'corpus/0xabad1dea.tweets') | ||||
|   INTERIM = :interim | ||||
| end | ||||
| 
 | ||||
| require 'twitter_ebooks/nlp' | ||||
|  |  | |||
|  | @ -39,9 +39,14 @@ module Ebooks | |||
|       end | ||||
|     end | ||||
| 
 | ||||
|     def initialize(username, path, client=nil) | ||||
|     def initialize(username, path=nil, client=nil) | ||||
|       @username = username | ||||
|       @path = path || "#{username}.json" | ||||
|       @path = path || "corpus/#{username}.json" | ||||
| 
 | ||||
|       if File.directory?(@path) | ||||
|         @path = File.join(@path, "#{username}.json") | ||||
|       end | ||||
| 
 | ||||
|       @client = client || make_client | ||||
| 
 | ||||
|       if File.exists?(@path) | ||||
|  |  | |||
							
								
								
									
										65
									
								
								lib/twitter_ebooks/bot.rb
									
										
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										65
									
								
								lib/twitter_ebooks/bot.rb
									
										
									
									
									
										
										
										Executable file → Normal file
									
								
							|  | @ -6,28 +6,11 @@ module Ebooks | |||
|   class ConfigurationError < Exception | ||||
|   end | ||||
| 
 | ||||
|   # Information about a particular Twitter user we know | ||||
|   class UserInfo | ||||
|     attr_reader :username | ||||
| 
 | ||||
|     # @return [Integer] how many times we can pester this user unprompted | ||||
|     attr_accessor :pesters_left | ||||
| 
 | ||||
|     def initialize(username) | ||||
|       @username = username | ||||
|       @pesters_left = 1 | ||||
|     end | ||||
| 
 | ||||
|     # @return [Boolean] true if we're allowed to pester this user | ||||
|     def can_pester? | ||||
|       @pesters_left > 0 | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   # Represents a single reply tree of tweets | ||||
|   class Conversation | ||||
|     attr_reader :last_update | ||||
| 
 | ||||
|     # @param bot [Ebooks::Bot] | ||||
|     def initialize(bot) | ||||
|       @bot = bot | ||||
|       @tweets = [] | ||||
|  | @ -90,6 +73,8 @@ module Ebooks | |||
|       @mentions.map(&:downcase).include?(@bot.username.downcase) && !@tweet.retweeted_status? && !@tweet.text.start_with?('RT ') | ||||
|     end | ||||
| 
 | ||||
|     # @param bot [Ebooks::Bot] | ||||
|     # @param ev [Twitter::Tweet] | ||||
|     def initialize(bot, ev) | ||||
|       @bot = bot | ||||
|       @tweet = ev | ||||
|  | @ -138,7 +123,7 @@ module Ebooks | |||
|     # @return [Hash{String => Ebooks::Conversation}] maps tweet ids to their conversation contexts | ||||
|     attr_accessor :conversations | ||||
|     # @return [Range, Integer] range of seconds to delay in delay method | ||||
|     attr_accessor :delay | ||||
|     attr_accessor :delay_range | ||||
| 
 | ||||
|     # @return [Array] list of all defined bots | ||||
|     def self.all; @@all ||= []; end | ||||
|  | @ -161,24 +146,17 @@ module Ebooks | |||
|     # @param b Block to call with new bot | ||||
|     def initialize(username, &b) | ||||
|       @blacklist ||= [] | ||||
|       @userinfo ||= {} | ||||
|       @conversations ||= {} | ||||
|       # Tweet ids we've already observed, to avoid duplication | ||||
|       @seen_tweets ||= {} | ||||
| 
 | ||||
|       @username = username | ||||
|       configure(*args, &b) | ||||
|       configure | ||||
| 
 | ||||
|       b.call(self) unless b.nil? | ||||
|       Bot.all << self | ||||
|     end | ||||
| 
 | ||||
|     # Find information we've collected about a user | ||||
|     # @param username [String] | ||||
|     # @return [Ebooks::UserInfo] | ||||
|     def userinfo(username) | ||||
|       @userinfo[username] ||= UserInfo.new(username) | ||||
|     end | ||||
| 
 | ||||
|     # Find or create the conversation context for this tweet | ||||
|     # @param tweet [Twitter::Tweet] | ||||
|     # @return [Ebooks::Conversation] | ||||
|  | @ -229,7 +207,7 @@ module Ebooks | |||
|     # Calculate some meta information about a tweet relevant for replying | ||||
|     # @param ev [Twitter::Tweet] | ||||
|     # @return [Ebooks::TweetMeta] | ||||
|     def calc_meta(ev) | ||||
|     def meta(ev) | ||||
|       TweetMeta.new(self, ev) | ||||
|     end | ||||
| 
 | ||||
|  | @ -255,7 +233,7 @@ module Ebooks | |||
|         return unless ev.text # If it's not a text-containing tweet, ignore it | ||||
|         return if ev.user.screen_name == @username # Ignore our own tweets | ||||
| 
 | ||||
|         meta = calc_meta(ev) | ||||
|         meta = meta(ev) | ||||
| 
 | ||||
|         if blacklisted?(ev.user.screen_name) | ||||
|           log "Blocking blacklisted user @#{ev.user.screen_name}" | ||||
|  | @ -273,9 +251,9 @@ module Ebooks | |||
|         if meta.mentions_bot? | ||||
|           log "Mention from @#{ev.user.screen_name}: #{ev.text}" | ||||
|           conversation(ev).add(ev) | ||||
|           fire(:mention, ev, meta) | ||||
|           fire(:mention, ev) | ||||
|         else | ||||
|           fire(:timeline, ev, meta) | ||||
|           fire(:timeline, ev) | ||||
|         end | ||||
| 
 | ||||
|       elsif ev.is_a?(Twitter::Streaming::DeletedTweet) || | ||||
|  | @ -290,7 +268,19 @@ module Ebooks | |||
|     def prepare | ||||
|       # Sanity check | ||||
|       if @username.nil? | ||||
|         raise ConfigurationError, "bot.username cannot be nil" | ||||
|         raise ConfigurationError, "bot username cannot be nil" | ||||
|       end | ||||
| 
 | ||||
|       if @consumer_key.nil? || @consumer_key.empty? || | ||||
|          @consumer_secret.nil? || @consumer_key.empty? | ||||
|         log "Missing consumer_key or consumer_secret. These details can be acquired by registering a Twitter app at https://apps.twitter.com/" | ||||
|         exit 1 | ||||
|       end | ||||
| 
 | ||||
|       if @access_token.nil? || @access_token.empty? || | ||||
|          @access_token_secret.nil? || @access_token_secret.empty? | ||||
|         log "Missing access_token or access_token_secret. Please run `ebooks auth`." | ||||
|         exit 1 | ||||
|       end | ||||
| 
 | ||||
|       twitter | ||||
|  | @ -346,20 +336,13 @@ module Ebooks | |||
|         log "Sending DM to @#{ev.sender.screen_name}: #{text}" | ||||
|         twitter.create_direct_message(ev.sender.screen_name, text, opts) | ||||
|       elsif ev.is_a? Twitter::Tweet | ||||
|         meta = calc_meta(ev) | ||||
|         meta = meta(ev) | ||||
| 
 | ||||
|         if conversation(ev).is_bot?(ev.user.screen_name) | ||||
|           log "Not replying to suspected bot @#{ev.user.screen_name}" | ||||
|           return false | ||||
|         end | ||||
| 
 | ||||
|         if !meta.mentions_bot? | ||||
|           if !userinfo(ev.user.screen_name).can_pester? | ||||
|             log "Not replying: leaving @#{ev.user.screen_name} alone" | ||||
|             return false | ||||
|           end | ||||
|         end | ||||
| 
 | ||||
|         log "Replying to @#{ev.user.screen_name} with: #{meta.reply_prefix + text}" | ||||
|         tweet = twitter.update(meta.reply_prefix + text, in_reply_to_status_id: ev.id) | ||||
|         conversation(tweet).add(tweet) | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| source 'http://rubygems.org' | ||||
| ruby '1.9.3' | ||||
| ruby '{{RUBY_VERSION}}' | ||||
| 
 | ||||
| gem 'twitter_ebooks' | ||||
|  |  | |||
|  | @ -1 +1 @@ | |||
| worker: ruby run.rb start | ||||
| worker: ebooks start | ||||
|  |  | |||
							
								
								
									
										59
									
								
								skeleton/bots.rb
									
										
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										59
									
								
								skeleton/bots.rb
									
										
									
									
									
										
										
										Executable file → Normal file
									
								
							|  | @ -1,42 +1,55 @@ | |||
| #!/usr/bin/env ruby | ||||
| 
 | ||||
| require 'twitter_ebooks' | ||||
| 
 | ||||
| # This is an example bot definition with event handlers commented out | ||||
| # You can define as many of these as you like; they will run simultaneously | ||||
| # You can define and instantiate as many bots as you like | ||||
| 
 | ||||
| Ebooks::Bot.new("{{BOT_NAME}}") do |bot| | ||||
|   # Consumer details come from registering an app at https://dev.twitter.com/ | ||||
|   # OAuth details can be fetched with https://github.com/marcel/twurl | ||||
|   bot.consumer_key = "" # Your app consumer key | ||||
|   bot.consumer_secret = "" # Your app consumer secret | ||||
|   bot.oauth_token = "" # Token connecting the app to this account | ||||
|   bot.oauth_token_secret = "" # Secret connecting the app to this account | ||||
| class MyBot < Ebooks::Bot | ||||
|   # Configuration here applies to all MyBots | ||||
|   def configure | ||||
|     # Consumer details come from registering an app at https://dev.twitter.com/ | ||||
|     # Once you have consumer details, use "ebooks auth" for new access tokens | ||||
|     self.consumer_key = '' # Your app consumer key | ||||
|     self.consumer_secret = '' # Your app consumer secret | ||||
| 
 | ||||
|   bot.on_message do |dm| | ||||
|     # Users to block instead of interacting with | ||||
|     self.blacklist = ['tnietzschequote'] | ||||
| 
 | ||||
|     # Range in seconds to randomize delay when bot.delay is called | ||||
|     self.delay_range = 1..6 | ||||
|   end | ||||
| 
 | ||||
|   def on_startup | ||||
|     scheduler.every '24h' do | ||||
|       # Tweet something every 24 hours | ||||
|       # See https://github.com/jmettraux/rufus-scheduler | ||||
|       # bot.tweet("hi") | ||||
|       # bot.pictweet("hi", "cuteselfie.jpg") | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def on_message(dm) | ||||
|     # Reply to a DM | ||||
|     # bot.reply(dm, "secret secrets") | ||||
|   end | ||||
| 
 | ||||
|   bot.on_follow do |user| | ||||
|   def on_follow(user) | ||||
|     # Follow a user back | ||||
|     # bot.follow(user[:screen_name]) | ||||
|   end | ||||
| 
 | ||||
|   bot.on_mention do |tweet, meta| | ||||
|   def on_mention(tweet) | ||||
|     # Reply to a mention | ||||
|     # bot.reply(tweet, meta[:reply_prefix] + "oh hullo") | ||||
|     # bot.reply(tweet, meta(tweet)[:reply_prefix] + "oh hullo") | ||||
|   end | ||||
| 
 | ||||
|   bot.on_timeline do |tweet, meta| | ||||
|   def on_timeline(tweet) | ||||
|     # Reply to a tweet in the bot's timeline | ||||
|     # bot.reply(tweet, meta[:reply_prefix] + "nice tweet") | ||||
|   end | ||||
| 
 | ||||
|   bot.scheduler.every '24h' do | ||||
|     # Tweet something every 24 hours | ||||
|     # See https://github.com/jmettraux/rufus-scheduler | ||||
|     # bot.tweet("hi") | ||||
|     # bot.pictweet("hi", "cuteselfie.jpg", ":possibly_sensitive => true") | ||||
|     # bot.reply(tweet, meta(tweet)[:reply_prefix] + "nice tweet") | ||||
|   end | ||||
| end | ||||
| 
 | ||||
| # Make a MyBot and attach it to an account | ||||
| MyBot.new("{{BOT_NAME}}") do |bot| | ||||
|   bot.access_token = "" # Token connecting the app to this account | ||||
|   bot.access_token_secret = "" # Secret connecting the app to this account | ||||
| end | ||||
|  |  | |||
|  | @ -1,9 +0,0 @@ | |||
| #!/usr/bin/env ruby | ||||
| 
 | ||||
| require_relative 'bots' | ||||
| 
 | ||||
| EM.run do | ||||
|  Ebooks::Bot.all.each do |bot| | ||||
|     bot.start | ||||
|   end | ||||
| end | ||||
|  | @ -7,7 +7,6 @@ class TestBot < Ebooks::Bot | |||
|   attr_accessor :twitter | ||||
| 
 | ||||
|   def configure | ||||
|     self.username = "test_ebooks" | ||||
|   end | ||||
| 
 | ||||
|   def on_direct_message(dm) | ||||
|  | @ -84,7 +83,7 @@ end | |||
| 
 | ||||
| describe Ebooks::Bot do | ||||
|   include Ebooks::Test | ||||
|   let(:bot) { TestBot.new } | ||||
|   let(:bot) { TestBot.new('test_ebooks') } | ||||
| 
 | ||||
|   before { Timecop.freeze } | ||||
|   after { Timecop.return } | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,18 +0,0 @@ | |||
| #!/usr/bin/env ruby | ||||
| # encoding: utf-8 | ||||
| 
 | ||||
| require 'twitter_ebooks' | ||||
| require 'minitest/autorun' | ||||
| require 'benchmark' | ||||
| 
 | ||||
| module Ebooks | ||||
|   class TestKeywords < Minitest::Test | ||||
|     corpus = NLP.normalize(File.read(ARGV[0])) | ||||
|     puts "Finding and ranking keywords" | ||||
|     puts Benchmark.measure { | ||||
|       NLP.keywords(corpus).top(50).each do |keyword| | ||||
|         puts "#{keyword.text} #{keyword.weight}" | ||||
|       end | ||||
|     } | ||||
|   end | ||||
| end | ||||
|  | @ -1,18 +0,0 @@ | |||
| #!/usr/bin/env ruby | ||||
| # encoding: utf-8 | ||||
| 
 | ||||
| require 'twitter_ebooks' | ||||
| require 'minitest/autorun' | ||||
| 
 | ||||
| module Ebooks | ||||
|   class TestTokenize < Minitest::Test | ||||
|     corpus = NLP.normalize(File.read(TEST_CORPUS_PATH)) | ||||
|     sents = NLP.sentences(corpus).sample(10) | ||||
| 
 | ||||
|     NLP.sentences(corpus).sample(10).each do |sent| | ||||
|       p sent | ||||
|       p NLP.tokenize(sent) | ||||
|       puts | ||||
|     end | ||||
|   end | ||||
| end | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Jaiden Mispy
						Jaiden Mispy