blackwinter-twitter2jabber 0.0.4

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.
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
+