retjilp 0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2abfa27b54c9c65411d42b2da87ef91861120ac1
4
+ data.tar.gz: 11990c7cbc4e08b08d845967a1677d8fab55ef0f
5
+ SHA512:
6
+ metadata.gz: 66febc755793570d43e7f8a528d78c54915fe2f160ba1106b4e93b63c0d160f2daefdec204e8889e1db99ec0901ca8449869cc3d6b4e46455cb418fa50452069
7
+ data.tar.gz: 8159a921b9d8d4046308c00ac44b9cb6f6a16b1ef31deab3704495d082cd5c617aca8802b3ed7d9c843da89987fdf26540cd6555ed097f9dd4461403437f046b
data/AUTHORS ADDED
@@ -0,0 +1 @@
1
+ Remko Tronçon (http://el-tramo.be)
data/COPYING ADDED
@@ -0,0 +1,25 @@
1
+ Copyright (c) 2011-2012 Remko Tronçon (http://el-tramo.be)
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+ * Redistributions of source code must retain the above copyright
7
+ notice, this list of conditions and the following disclaimer.
8
+ * Redistributions in binary form must reproduce the above copyright
9
+ notice, this list of conditions and the following disclaimer in the
10
+ documentation and/or other materials provided with the distribution.
11
+ * Neither the name of the <organization> nor the
12
+ names of its contributors may be used to endorse or promote products
13
+ derived from this software without specific prior written permission.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
+ DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
19
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
+
@@ -0,0 +1,25 @@
1
+ Retjilp: Native Auto-retweet bot
2
+ --------------------------------
3
+ <http://el-tramo.be/blog/retjilp>
4
+
5
+ Retjilp logs into your account, scans all the tweets from your following
6
+ list or another defined list for a set of matching words, and retweets
7
+ the ones that match (using the native retweet API).
8
+
9
+ Prerequisites of this script are Ruby and the `oauth` and `json_pure`
10
+ ruby gems (`gem install <gem>`).
11
+
12
+ To use this script, you will need to have registered an application with
13
+ <http://twitter.com/apps> to get a consumer key and secret, and fill these
14
+ values in the `config` file. After having changed the `config` file, move it
15
+ to a dir `.retjilp` in your homedir (i.e. `~/.retjilp`).
16
+
17
+ To start the script, run
18
+
19
+ ./retjilp.rb
20
+
21
+ To get a list of command-line parameters, use the `--help` option.
22
+
23
+ The first time the script is run, it will ask you to authorize the application
24
+ in your twitter account. After this is done, the script will automatically log
25
+ in the next time it is run.
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib'))
4
+
5
+ require 'retjilp/runner'
6
+
7
+ Retjilp::Runner.new(ARGV).run
data/config ADDED
@@ -0,0 +1,27 @@
1
+ /*
2
+ * Retjilp configuration file.
3
+ *
4
+ * Change this to reflect your setup, and put it in ~/.retjilp
5
+ */
6
+ {
7
+ /*
8
+ * Consumer key and secret.
9
+ * Get this by registering a new (desktop) application at
10
+ * http://twitter.com/apps
11
+ */
12
+ "consumer_key": "abcdeFghIjklMnOpQrStUv",
13
+ "consumer_secret": "abcdefgh123456789abcdefgh123456789abcdefg",
14
+
15
+ /*
16
+ * The strings that a tweet should be matched against.
17
+ * These strings are matched in lower case.
18
+ */
19
+ "match": ["#sometag", "#someothertag", "someword"]
20
+
21
+ /*
22
+ * List name from which statuses are retweeted.
23
+ * Set this config value if you want to retweet only from
24
+ * this list instead of your following list.
25
+ */
26
+ /* "retweet_from_list": "auto-retweet" */
27
+ }
@@ -0,0 +1,24 @@
1
+ require 'optparse'
2
+ require 'logger'
3
+
4
+ module Retjilp
5
+ class Options
6
+ attr_reader :log_level
7
+
8
+ def initialize(argv)
9
+ @log_level = Logger::WARN
10
+ OptionParser.new do |opts|
11
+ opts.banner = "Usage: retjilp [ --help ] [ --verbose | --debug ]"
12
+ opts.on("--verbose", "Run with verbose output") { @log_level = Logger::INFO }
13
+ opts.on("--debug", "Run with debug output") { @log_level = Logger::DEBUG }
14
+ opts.on_tail("-h", "--help", "Show this help") { puts opts ; exit }
15
+ begin
16
+ opts.parse!(argv)
17
+ rescue => e
18
+ STDERR.puts e.message, "\n", opts
19
+ exit(-1)
20
+ end
21
+ end.parse!(argv)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,142 @@
1
+ require 'rubygems'
2
+ require 'oauth'
3
+ require 'json/pure'
4
+ require 'logger'
5
+
6
+ require_relative 'options'
7
+
8
+ module Retjilp
9
+ TWITTER_URI = "http://api.twitter.com"
10
+ API_VERSION = "1.1"
11
+
12
+ class Retweeter
13
+ def initialize(options)
14
+ @log = Logger.new(STDERR)
15
+ @log.formatter = proc { |severity, time, prog, msg| "#{severity}: #{msg}\n" }
16
+ @log.level = options.log_level
17
+ end
18
+
19
+ def run
20
+ # Initialize data dir
21
+ @log.info("Reading configuration file")
22
+ data_dir = File.expand_path("~/.retjilp")
23
+ config_filename = File.join(data_dir, "config")
24
+ access_token_filename = File.join(data_dir, "access_token")
25
+
26
+ # Read configuration file
27
+ begin
28
+ config = File.open(config_filename) { |f| JSON.load(f) }
29
+ rescue => e
30
+ fatal_exit("Error parsing configuration file #{config_filename}: #{e.message}")
31
+ end
32
+
33
+ # Initialize the access token
34
+ access_token = nil
35
+ user_info = nil
36
+ if File.exist?(access_token_filename)
37
+ # Try using the cached token
38
+ @log.info("Loading cached access token from #{access_token_filename}")
39
+ File.open(access_token_filename) do |f|
40
+ begin
41
+ access_token_data = JSON.load(f)
42
+ consumer = OAuth::Consumer.new(config["consumer_key"], config["consumer_secret"], { :site => TWITTER_URI })
43
+ access_token = OAuth::AccessToken.new(consumer, access_token_data["token"], access_token_data["secret"])
44
+ unless user_info = verify_token(access_token)
45
+ @log.warn("Cached token not authorized")
46
+ access_token = nil
47
+ end
48
+ rescue JSON::ParserError
49
+ @log.warn("Cached token does not parse")
50
+ end
51
+ end
52
+ end
53
+
54
+ # Request the token if the cached access token does not exist
55
+ unless access_token
56
+ STDIN.tty? or fatal_exit("This script must be run interactively the first time to be able to authenticate.")
57
+ @log.info("Requesting new access token")
58
+ consumer = OAuth::Consumer.new(
59
+ config["consumer_key"],
60
+ config["consumer_secret"],
61
+ :site => TWITTER_URI,
62
+ :scheme => :header,
63
+ :request_token_path => "/oauth/request_token",
64
+ :authorize_path => "/oauth/authorize",
65
+ :access_token_path => "/oauth/access_token",
66
+ :http_method => :post)
67
+ request_token = consumer.get_request_token(:oauth_callback => "oob")
68
+
69
+ puts "Please open #{request_token.authorize_url} in your browser, authorize Retjilp, and enter the PIN code below:"
70
+ verifier = STDIN.gets.chomp
71
+
72
+ begin
73
+ access_token = request_token.get_access_token(:oauth_verifier => verifier)
74
+ rescue OAuth::Unauthorized
75
+ fatal_exit("Invalid PIN verification!")
76
+ end
77
+ user_info = verify_token(access_token) or fatal_exit("Access token not authorized!")
78
+ @log.info("Caching token in #{access_token_filename}")
79
+ File.open(access_token_filename, 'w+') do |f|
80
+ access_token_data = {
81
+ "token" => access_token.token,
82
+ "secret" => access_token.secret
83
+ }
84
+ JSON.dump(access_token_data, f)
85
+ end
86
+ end
87
+
88
+ @log.info("Logged in as #{user_info["screen_name"]}")
89
+
90
+ # Get a list of retweeted ids
91
+ @log.info("Fetching retweets")
92
+ retweets = JSON.parse(access_token.get("/#{API_VERSION}/statuses/user_timeline.json?trim_user=true&include_rts=true").body)
93
+ @log.debug(JSON.pretty_generate(retweets))
94
+ not retweets.include? "error" or fatal_exit("Error fetching retweets: #{retweets}")
95
+
96
+ retweeted_ids = retweets.map { |retweet| retweet["retweeted_status"]["id"] }.sort!
97
+
98
+ # Fetch the statuses
99
+ @log.info("Fetching friends statuses")
100
+ if config["retweet_from_list"]
101
+ status_uri = "/#{API_VERSION}/lists/statuses.json?slug=#{config["retweet_from_list"]}&owner_screen_name=#{user_info["screen_name"]}&include_rts=true"
102
+ else
103
+ status_uri = "/#{API_VERSION}/statuses/home_timeline.json?trim_user=true"
104
+ end
105
+ status_uri += "&since_id=#{retweeted_ids[0]}" unless retweeted_ids.empty?
106
+ statuses = JSON.parse(access_token.get(status_uri).body)
107
+ @log.debug(JSON.pretty_generate(statuses))
108
+ not statuses.include? "error" or fatal_exit("Error fetching statuses: #{statuses.to_s}")
109
+
110
+ # Retweet statuses
111
+ statuses.each do |status|
112
+ should_retweet = (config["match"].empty? or config["match"].any? { |match|
113
+ status["text"].downcase.include? match.downcase
114
+ })
115
+ if should_retweet
116
+ id_to_retweet = status.has_key?("retweeted_status") ? status["retweeted_status"]["id"] : status["id"]
117
+ if retweeted_ids.include? id_to_retweet
118
+ @log.debug("Already retweeted: #{status["text"]}")
119
+ else
120
+ @log.info("Retweeting: #{status["text"]}")
121
+ result = access_token.post("/#{API_VERSION}/statuses/retweet/#{id_to_retweet}.json")
122
+ result.class == Net::HTTPOK or @log.error("Error retweeting #{result.body}")
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ private
129
+ # Print a log message, and exit
130
+ def fatal_exit(msg)
131
+ @log.fatal(msg)
132
+ exit -1
133
+ end
134
+
135
+ # Helper method to verify the validity of an access token.
136
+ # Returns the user info if the token verified correctly.
137
+ def verify_token(token)
138
+ response = token.get("/#{API_VERSION}/account/verify_credentials.json")
139
+ response.class == Net::HTTPOK ? JSON.parse(response.body) : nil
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,14 @@
1
+ require_relative 'options'
2
+ require_relative 'retweeter'
3
+
4
+ module Retjilp
5
+ class Runner
6
+ def initialize(argv)
7
+ @options = Options.new(argv)
8
+ end
9
+
10
+ def run
11
+ Retweeter.new(@options).run
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'retjilp'
5
+ s.summary = 'Automatically retweet tweets'
6
+ s.description = 'Retjilp logs into your account, scans all the tweets from your following list or another defined list for a set of matching words, and retweets the ones that match (using the native retweet API).'
7
+ s.requirements = ['']
8
+ s.version = '0.2'
9
+ s.author = 'Remko Tronçon'
10
+ s.email = 'remko@el-tramo.be'
11
+ s.homepage = 'http://el-tramo.be/blog/retjilp'
12
+ s.platform = Gem::Platform::RUBY
13
+ s.required_ruby_version = '>=1.8'
14
+ s.files = Dir['**/**']
15
+ s.executables = 'retjilp'
16
+ s.require_paths = ['lib']
17
+ s.has_rdoc = false
18
+ s.license = 'BSD'
19
+
20
+ s.add_runtime_dependency('oauth')
21
+ s.add_runtime_dependency('json_pure')
22
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: retjilp
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.2'
5
+ platform: ruby
6
+ authors:
7
+ - Remko Tronçon
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-03-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: oauth
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json_pure
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Retjilp logs into your account, scans all the tweets from your following
42
+ list or another defined list for a set of matching words, and retweets the ones
43
+ that match (using the native retweet API).
44
+ email: remko@el-tramo.be
45
+ executables:
46
+ - retjilp
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - AUTHORS
51
+ - bin/retjilp
52
+ - config
53
+ - COPYING
54
+ - lib/retjilp/options.rb
55
+ - lib/retjilp/retweeter.rb
56
+ - lib/retjilp/runner.rb
57
+ - README.markdown
58
+ - retjilp.gemspec
59
+ homepage: http://el-tramo.be/blog/retjilp
60
+ licenses:
61
+ - BSD
62
+ metadata: {}
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - '>='
70
+ - !ruby/object:Gem::Version
71
+ version: '1.8'
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements:
78
+ - ''
79
+ rubyforge_project:
80
+ rubygems_version: 2.0.0
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: Automatically retweet tweets
84
+ test_files: []