blackwinter-twitter2jabber 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,43 @@
1
+ = twitter2jabber - Twitter-to-Jabber gateway.
2
+
3
+ == VERSION
4
+
5
+ This documentation refers to twitter2jabber version 0.0.4
6
+
7
+
8
+ == DESCRIPTION
9
+
10
+ Twitter-to-Jabber gateway.
11
+
12
+ Based on a script[http://da.nmilne.com/?p=353] by dkam. See also his
13
+ Jitter[http://github.com/dkam/jitter] project.
14
+
15
+
16
+ == LINKS
17
+
18
+ <b></b>
19
+ Documentation:: <http://twitter2jabber.rubyforge.org/>
20
+ Source code:: <http://github.com/blackwinter/twitter2jabber>
21
+ Rubyforge project:: <http://rubyforge.org/projects/twitter2jabber>
22
+
23
+
24
+ == AUTHORS
25
+
26
+ * Jens Wille <mailto:ww@blackwinter.de>
27
+
28
+
29
+ == LICENSE AND COPYRIGHT
30
+
31
+ Copyright (C) 2009 Jens Wille
32
+
33
+ twitter2jabber is free software: you can redistribute it and/or modify it
34
+ under the terms of the GNU General Public License as published by the Free
35
+ Software Foundation, either version 3 of the License, or (at your option)
36
+ any later version.
37
+
38
+ twitter2jabber is distributed in the hope that it will be useful, but WITHOUT
39
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
40
+ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
41
+
42
+ You should have received a copy of the GNU General Public License along with
43
+ twitter2jabber. If not, see <http://www.gnu.org/licenses/>.
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ require %q{lib/twitter2jabber/version}
2
+
3
+ begin
4
+ require 'hen'
5
+
6
+ Hen.lay! {{
7
+ :rubyforge => {
8
+ :project => %q{twitter2jabber},
9
+ :package => %q{twitter2jabber},
10
+ :rdoc_dir => nil
11
+ },
12
+
13
+ :gem => {
14
+ :version => Twitter2Jabber::VERSION,
15
+ :summary => %q{Twitter-to-Jabber gateway.},
16
+ :homepage => %q{http://twitter2jabber.rubyforge.org/},
17
+ :files => FileList['lib/**/*.rb', 'bin/*'].to_a,
18
+ :extra_files => FileList['[A-Z]*', 'sample/**/*'].to_a,
19
+ :dependencies => %w[twitter xmpp4r-simple highline]
20
+ }
21
+ }}
22
+ rescue LoadError
23
+ abort "Please install the 'hen' gem first."
24
+ end
25
+
26
+ ### Place your custom Rake tasks here.
data/TODO ADDED
@@ -0,0 +1,4 @@
1
+ - persistent cache! (friends_timeline: since/since_id?)
2
+ - better interrupt handling (in loop mode)
3
+ - daemonize after asking for credentials (in loop mode)
4
+ - additional commands?
@@ -0,0 +1,169 @@
1
+ #! /usr/bin/ruby
2
+
3
+ #--
4
+ ###############################################################################
5
+ # #
6
+ # twitter2jabber - Twitter-to-Jabber gateway. #
7
+ # #
8
+ # Copyright (C) 2009 Jens Wille #
9
+ # #
10
+ # Authors: #
11
+ # Jens Wille <ww@blackwinter.de> #
12
+ # #
13
+ # twitter2jabber is free software; you can redistribute it and/or modify it #
14
+ # under the terms of the GNU General Public License as published by the Free #
15
+ # Software Foundation; either version 3 of the License, or (at your option) #
16
+ # any later version. #
17
+ # #
18
+ # twitter2jabber is distributed in the hope that it will be useful, but #
19
+ # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY #
20
+ # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
21
+ # more details. #
22
+ # #
23
+ # You should have received a copy of the GNU General Public License along #
24
+ # with twitter2jabber. If not, see <http://www.gnu.org/licenses/>. #
25
+ # #
26
+ ###############################################################################
27
+ #++
28
+
29
+ require 'optparse'
30
+ require 'yaml'
31
+
32
+ require 'rubygems'
33
+ require 'highline/import'
34
+
35
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
36
+
37
+ require 'twitter2jabber'
38
+
39
+ USAGE = "Usage: #{$0} [-h|--help] [options]"
40
+
41
+ def ask(prompt)
42
+ super
43
+ rescue Interrupt
44
+ puts
45
+ exit
46
+ end
47
+
48
+ options = {
49
+ :config => 'config.yaml',
50
+ :user => nil,
51
+ :jid => nil,
52
+ :recipients => [],
53
+ :formats => nil,
54
+ :template_dir => nil,
55
+ :loop => false,
56
+ :pause => nil,
57
+ :verbose => false,
58
+ :debug => false
59
+ }
60
+
61
+ OptionParser.new { |opts|
62
+ opts.banner = USAGE
63
+
64
+ opts.separator ' '
65
+ opts.separator 'Options:'
66
+
67
+ opts.on('-c', '--config YAML', "Path to config file [Default: #{options[:config]}]") { |c|
68
+ options[:config] = c
69
+ }
70
+
71
+ opts.separator ' '
72
+
73
+ opts.on('-u', '--user NAME', 'Twitter user') { |u|
74
+ options[:user] = u
75
+ }
76
+
77
+ opts.on('-j', '--jid ID', 'Jabber ID') { |j|
78
+ options[:jid] = j
79
+ }
80
+
81
+ opts.separator ' '
82
+
83
+ opts.on('-r', '--recipients LIST', 'Comma-separated list of Jabber IDs') { |r|
84
+ options[:recipients] += r.split(',')
85
+ }
86
+
87
+ opts.on('-R', '--recipients-from-file FILE', 'File with one Jabber ID per line') { |r|
88
+ options[:recipients] += File.readlines(r).map { |l| l.chomp }
89
+ }
90
+
91
+ opts.separator ' '
92
+
93
+ opts.on('-f', '--formats LIST', "Comma-separated list of formats [Default: #{Twitter2Jabber::DEFAULT_FORMATS.join(',')}]") { |f|
94
+ options[:formats] = f.split(',')
95
+ }
96
+
97
+ opts.on('-t', '--template-dir PATH', 'Path to custom template directory', "[Default: #{Twitter2Jabber::DEFAULT_TEMPLATES}]") { |t|
98
+ abort "Template directory not found: #{t}" unless File.directory?(t)
99
+ options[:template_dir] = t
100
+ }
101
+
102
+ opts.separator ' '
103
+
104
+ opts.on('-l', '--loop', 'Run in a loop') {
105
+ options[:loop] = true
106
+ }
107
+
108
+ opts.on('-p', '--pause SECONDS', Integer, 'Number of seconds to pause between loops') { |p|
109
+ options[:pause] = p
110
+ }
111
+
112
+ opts.separator ' '
113
+ opts.separator 'Generic options:'
114
+
115
+ opts.on('-v', '--verbose', 'Be verbose') {
116
+ options[:verbose] = true
117
+ }
118
+
119
+ opts.on('-D', '--debug', "Print debug output and don't send or update anything") {
120
+ options[:debug] = true
121
+ }
122
+
123
+ opts.on('-h', '--help', 'Print this help message and exit') {
124
+ puts opts
125
+ exit
126
+ }
127
+
128
+ opts.on('--version', 'Print program version and exit') {
129
+ puts "#{File.basename($0)} v#{Twitter2Jabber::VERSION}"
130
+ exit
131
+ }
132
+ }.parse!
133
+
134
+ config = begin
135
+ YAML.load_file(options[:config])
136
+ rescue Errno::ENOENT
137
+ {}
138
+ end
139
+
140
+ # twitter config
141
+ config[:twitter] ||= {}
142
+ config[:twitter][:user] = options[:user] if options[:user]
143
+ config[:twitter][:user] ||= ask('Twitter user: ')
144
+ config[:twitter][:pass] ||= ask("Password for Twitter user #{config[:twitter][:user]}: ") { |q| q.echo = false }
145
+
146
+ # jabber config
147
+ config[:jabber] ||= {}
148
+ config[:jabber][:user] = options[:jid] if options[:jid]
149
+ config[:jabber][:user] ||= ask('Jabber ID: ')
150
+ config[:jabber][:pass] ||= ask("Password for Jabber ID #{config[:jabber][:user]}: ") { |q| q.echo = false }
151
+
152
+ recipients = options[:recipients] + (config.delete(:recipients) || [])
153
+ recipients.uniq!
154
+
155
+ [:formats, :template_dir, :verbose, :debug].each { |key|
156
+ config[key] = options[key] if options[key]
157
+ }
158
+
159
+ begin
160
+ twitter2jabber = Twitter2Jabber.new(config)
161
+
162
+ if options[:loop]
163
+ twitter2jabber.loop(recipients, options[:pause] || config[:pause])
164
+ else
165
+ twitter2jabber.run(recipients)
166
+ end
167
+ rescue RuntimeError => err
168
+ abort err
169
+ end
@@ -0,0 +1,27 @@
1
+ class Twitter2Jabber
2
+
3
+ module Version
4
+
5
+ MAJOR = 0
6
+ MINOR = 0
7
+ TINY = 4
8
+
9
+ class << self
10
+
11
+ # Returns array representation.
12
+ def to_a
13
+ [MAJOR, MINOR, TINY]
14
+ end
15
+
16
+ # Short-cut for version string.
17
+ def to_s
18
+ to_a.join('.')
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+
25
+ VERSION = Version.to_s
26
+
27
+ end
@@ -0,0 +1,299 @@
1
+ #--
2
+ ###############################################################################
3
+ # #
4
+ # twitter2jabber - Twitter-to-Jabber gateway. #
5
+ # #
6
+ # Copyright (C) 2009 Jens Wille #
7
+ # #
8
+ # Authors: #
9
+ # Jens Wille <ww@blackwinter.de> #
10
+ # #
11
+ # twitter2jabber is free software; you can redistribute it and/or modify it #
12
+ # under the terms of the GNU General Public License as published by the Free #
13
+ # Software Foundation; either version 3 of the License, or (at your option) #
14
+ # any later version. #
15
+ # #
16
+ # twitter2jabber is distributed in the hope that it will be useful, but #
17
+ # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY #
18
+ # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
19
+ # more details. #
20
+ # #
21
+ # You should have received a copy of the GNU General Public License along #
22
+ # with twitter2jabber. If not, see <http://www.gnu.org/licenses/>. #
23
+ # #
24
+ ###############################################################################
25
+ #++
26
+
27
+ require 'time'
28
+ require 'erb'
29
+
30
+ require 'rubygems'
31
+ require 'twitter'
32
+ require 'xmpp4r-simple'
33
+
34
+ require 'twitter2jabber/version'
35
+
36
+ class Twitter2Jabber
37
+
38
+ MAX_LENGTH = 140
39
+
40
+ DEFAULT_PAUSE = 60
41
+
42
+ DEFAULT_FORMATS = %w[txt]
43
+
44
+ DEFAULT_TEMPLATES = File.expand_path(File.join(File.dirname(__FILE__), %w[.. sample templates]))
45
+
46
+ JABBER_NS = 'http://jabber.org/protocol/xhtml-im'
47
+ XHTML_NS = 'http://www.w3.org/1999/xhtml'
48
+
49
+ def self.loop(options, recipients = [], pause = nil, &block)
50
+ new(options).loop(recipients, pause, &block)
51
+ end
52
+
53
+ def self.run(options, recipients = [], &block)
54
+ new(options).run(recipients, &block)
55
+ end
56
+
57
+ attr_reader :id, :verbose, :debug, :twitter, :jabber, :filter, :formats, :templates
58
+
59
+ def initialize(options, &block)
60
+ [:twitter, :jabber].each { |client|
61
+ raise ArgumentError, "#{client} config missing" unless options[client].is_a?(Hash)
62
+ }
63
+
64
+ @id = "#{options[:twitter][:user]} -> #{options[:jabber][:user]}"
65
+
66
+ @verbose = options[:verbose]
67
+ @debug = options[:debug]
68
+
69
+ @twitter = twitter_connect(options[:twitter])
70
+ @jabber = jabber_connect(options[:jabber])
71
+
72
+ @filter = options[:filter] || block
73
+ @formats = options[:formats] || DEFAULT_FORMATS
74
+
75
+ @templates = Dir[
76
+ File.join(options[:template_dir] || DEFAULT_TEMPLATES, 'tweet.*')
77
+ ].inject({}) { |hash, template|
78
+ hash.update(File.extname(template).sub(/\A\./, '') => File.read(template))
79
+ }
80
+ end
81
+
82
+ def run(recipients = [], seen = {}, flag = true, &block)
83
+ deliver_tweets(recipients, seen, &block) if flag
84
+ post_messages
85
+ end
86
+
87
+ def loop(recipients = [], pause = nil, &block)
88
+ pause ||= DEFAULT_PAUSE
89
+
90
+ # jabber/twitter ratio
91
+ ratio = 10
92
+ pause /= ratio
93
+
94
+ # sleep at least one second
95
+ pause = 1 if pause < 1
96
+
97
+ i, seen = 0, Hash.new { |h, k| h[k] = true; false }
98
+
99
+ trap(:INT) { i = nil }
100
+
101
+ while i
102
+ i += 1
103
+
104
+ run(recipients, seen, i % ratio == 1, &block)
105
+
106
+ sleep pause
107
+ end
108
+ end
109
+
110
+ def deliver_tweets(recipients, seen = {}, &block)
111
+ get_tweets.each { |tweet|
112
+ next if seen[tweet.id]
113
+
114
+ logt tweet.id
115
+
116
+ # apply filters
117
+ next if filter && !filter[tweet]
118
+ next if block && !block[tweet]
119
+
120
+ msg = format_tweet(tweet)
121
+
122
+ recipients.each { |recipient|
123
+ deliver(recipient, msg)
124
+ }
125
+
126
+ sleep 1
127
+ }
128
+ end
129
+
130
+ def post_messages
131
+ jabber.received_messages { |msg|
132
+ next unless msg.type == :chat
133
+
134
+ logj msg.id
135
+
136
+ handle_command(msg.body, msg.from)
137
+ }
138
+ end
139
+
140
+ private
141
+
142
+ def twitter_connect(options)
143
+ auth = Twitter::HTTPAuth.new(options[:user], options[:pass])
144
+ client = Twitter::Base.new(auth)
145
+
146
+ # verify credentials
147
+ client.verify_credentials
148
+
149
+ logt "connected #{Time.now}"
150
+
151
+ client
152
+ rescue Twitter::TwitterError => err
153
+ raise "Can't connect to Twitter with ID '#{options[:user]}': #{err}"
154
+ end
155
+
156
+ def jabber_connect(options)
157
+ client = Jabber::Simple.new(options[:user], options[:pass])
158
+
159
+ logj "connected #{Time.now}"
160
+
161
+ client
162
+ rescue Jabber::JabberError => err
163
+ raise "Can't connect to Jabber with JID '#{options[:user]}': #{err}"
164
+ end
165
+
166
+ def get_tweets
167
+ twitter.friends_timeline.sort_by { |tweet|
168
+ tweet.created_at = Time.parse(tweet.created_at)
169
+ }
170
+ rescue Twitter::CantConnect
171
+ sleep pause
172
+ retry
173
+ end
174
+
175
+ def format_tweet(tweet)
176
+ user = tweet.user
177
+
178
+ msg = Jabber::Message.new.set_type(:chat)
179
+
180
+ formats.each { |format|
181
+ if template = templates[format]
182
+ msg.add_element format_element(format) {
183
+ ERB.new(template).result(binding)
184
+ }
185
+ end
186
+ }
187
+
188
+ msg
189
+ end
190
+
191
+ # cf. <http://devblog.famundo.com/articles/2006/10/18/ruby-and-xmpp-jabber-part-3-adding-html-to-the-messages>
192
+ def format_element(format)
193
+ body = REXML::Element.new('body')
194
+ REXML::Text.new(yield, format != 'html', body, true, nil, /.^/)
195
+
196
+ case format
197
+ when 'html'
198
+ html = REXML::Element.new('html').add_namespace(JABBER_NS)
199
+ html.add(body.add_namespace(XHTML_NS))
200
+ html
201
+ else
202
+ body
203
+ end
204
+ end
205
+
206
+ def handle_command(body, from, execute = true)
207
+ case body
208
+ when /\Ahe?(?:lp)?\z/i
209
+ deliver(from, <<-HELP) if execute
210
+ h[e[lp]] -- Print this help
211
+
212
+ de[bug] -- Print debug mode
213
+ de[bug] on|off -- Turn debug mode on/off
214
+
215
+ bl[ock] #ID -- Block ID
216
+ fa[v[orite]] #ID -- Create favorite #ID
217
+
218
+ re[ply] #ID: ... -- Reply to ID
219
+ le[n[gth]] ... -- Determine length
220
+ ... -- Update status
221
+
222
+ (Note: Message body must be shorter than #{MAX_LENGTH} characters)
223
+ HELP
224
+ when /\Ade(?:bug)?(?:\s+(on|off))?\z/i
225
+ if execute
226
+ flag = $1.downcase if $1
227
+
228
+ case flag
229
+ when 'on'
230
+ @debug = true
231
+ when 'off'
232
+ @debug = false
233
+ end
234
+
235
+ deliver(from, "DEBUG = #{debug ? 'on' : 'off'}")
236
+ end
237
+ when /\Abl(?:ock)?\s+#?(\d+)\z/i
238
+ twitter.block($1) if execute && !debug
239
+ when /\Afav?(?:orite)?\s+#?(\d+)\z/i
240
+ twitter.favorite_create($1) if execute && !debug
241
+ else
242
+ options = {}
243
+
244
+ if execute && body.sub!(/\Alen?(?:gth)?\s+/i, '')
245
+ if body = handle_command(body, from, false)
246
+ length = body.length
247
+ hint = length <= MAX_LENGTH ? 'OK' : 'TOO LONG'
248
+
249
+ deliver(from, "#{length} [#{hint}]: #{body}")
250
+ end
251
+
252
+ return
253
+ end
254
+
255
+ if body.sub!(/\Are(?:ply)?\s+#?(\d+):?\s+/i, '')
256
+ options[:in_reply_to_status_id] = $1
257
+ end
258
+
259
+ return body unless execute
260
+
261
+ if body.length <= MAX_LENGTH
262
+ update(body, options)
263
+ else
264
+ deliver(from, "MSG TOO LONG (> #{MAX_LENGTH}): #{body}")
265
+ end
266
+ end
267
+ end
268
+
269
+ def deliver(recipient, msg)
270
+ if debug
271
+ logj "#{recipient}: #{msg}", true
272
+ return
273
+ end
274
+
275
+ jabber.deliver(recipient, msg)
276
+ end
277
+
278
+ def update(msg, options = {})
279
+ if debug
280
+ logt "#{msg} (#{options.inspect})", true
281
+ return
282
+ end
283
+
284
+ twitter.update(msg, options)
285
+ end
286
+
287
+ def log(msg, verbose = verbose)
288
+ warn "[#{id}] #{msg}" if verbose
289
+ end
290
+
291
+ def logt(msg, verbose = verbose)
292
+ log("TWITTER #{msg}", verbose)
293
+ end
294
+
295
+ def logj(msg, verbose = verbose)
296
+ log("JABBER #{msg}", verbose)
297
+ end
298
+
299
+ end
@@ -0,0 +1,15 @@
1
+ ---
2
+ :twitter:
3
+ :user: foo
4
+ :pass: bar
5
+
6
+ :jabber:
7
+ :user: quix
8
+ :pass: quux
9
+
10
+ :recipients:
11
+ - one
12
+ - two
13
+ - three
14
+
15
+ :pause: 123
@@ -0,0 +1,7 @@
1
+ <%= user.name %>
2
+ (<a href="http://twitter.com/<%= user.screen_name %>"><%= user.screen_name %></a>)
3
+ [<%= tweet.created_at %>]:
4
+ <br /><br />
5
+ <img src="<%= user.profile_image_url %>" alt="[<%= user.screen_name %>]" />
6
+ <%= tweet.text %>
7
+ | #<%= tweet.id %>
@@ -0,0 +1,3 @@
1
+ <%= user.name %> (<%= user.screen_name %>) [<%= tweet.created_at %>]:
2
+
3
+ <%= tweet.text %> | #<%= tweet.id %>
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: blackwinter-twitter2jabber
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ platform: ruby
6
+ authors:
7
+ - Jens Wille
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-08-02 00:00:00 -07:00
13
+ default_executable: twitter2jabber
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: twitter
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: xmpp4r-simple
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: highline
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ description: Twitter-to-Jabber gateway.
46
+ email: jens.wille@uni-koeln.de
47
+ executables:
48
+ - twitter2jabber
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - COPYING
53
+ - ChangeLog
54
+ - README
55
+ files:
56
+ - lib/twitter2jabber.rb
57
+ - lib/twitter2jabber/version.rb
58
+ - bin/twitter2jabber
59
+ - COPYING
60
+ - Rakefile
61
+ - README
62
+ - ChangeLog
63
+ - TODO
64
+ - sample/config.yaml
65
+ - sample/templates
66
+ - sample/templates/tweet.html
67
+ - sample/templates/tweet.txt
68
+ has_rdoc: false
69
+ homepage: http://twitter2jabber.rubyforge.org/
70
+ licenses:
71
+ post_install_message:
72
+ rdoc_options:
73
+ - --line-numbers
74
+ - --inline-source
75
+ - --title
76
+ - twitter2jabber Application documentation
77
+ - --main
78
+ - README
79
+ - --charset
80
+ - UTF-8
81
+ - --all
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: "0"
89
+ version:
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: "0"
95
+ version:
96
+ requirements: []
97
+
98
+ rubyforge_project: twitter2jabber
99
+ rubygems_version: 1.3.5
100
+ signing_key:
101
+ specification_version: 3
102
+ summary: Twitter-to-Jabber gateway.
103
+ test_files: []
104
+