twitter2jabber 0.0.2 → 0.0.3
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 +1 -1
- data/Rakefile +1 -1
- data/TODO +4 -4
- data/bin/twitter2jabber +33 -6
- data/lib/twitter2jabber/version.rb +1 -1
- data/lib/twitter2jabber.rb +182 -16
- data/sample/templates/tweet.html +7 -0
- data/sample/templates/tweet.txt +3 -0
- metadata +4 -2
data/README
CHANGED
data/Rakefile
CHANGED
@@ -15,7 +15,7 @@ begin
|
|
15
15
|
:summary => %q{Twitter-to-Jabber gateway.},
|
16
16
|
:homepage => %q{http://twitter2jabber.rubyforge.org/},
|
17
17
|
:files => FileList['lib/**/*.rb', 'bin/*'].to_a,
|
18
|
-
:extra_files => FileList['[A-Z]*', 'sample
|
18
|
+
:extra_files => FileList['[A-Z]*', 'sample/**/*'].to_a,
|
19
19
|
:dependencies => %w[twitter xmpp4r-simple highline]
|
20
20
|
}
|
21
21
|
}}
|
data/TODO
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
- persistent cache!
|
2
|
-
-
|
3
|
-
-
|
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?
|
data/bin/twitter2jabber
CHANGED
@@ -46,12 +46,16 @@ rescue Interrupt
|
|
46
46
|
end
|
47
47
|
|
48
48
|
options = {
|
49
|
-
:config
|
50
|
-
:user
|
51
|
-
:jid
|
52
|
-
:recipients
|
53
|
-
:
|
54
|
-
:
|
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
|
55
59
|
}
|
56
60
|
|
57
61
|
OptionParser.new { |opts|
|
@@ -86,6 +90,17 @@ OptionParser.new { |opts|
|
|
86
90
|
|
87
91
|
opts.separator ' '
|
88
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
|
+
|
89
104
|
opts.on('-l', '--loop', 'Run in a loop') {
|
90
105
|
options[:loop] = true
|
91
106
|
}
|
@@ -97,6 +112,14 @@ OptionParser.new { |opts|
|
|
97
112
|
opts.separator ' '
|
98
113
|
opts.separator 'Generic options:'
|
99
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
|
+
|
100
123
|
opts.on('-h', '--help', 'Print this help message and exit') {
|
101
124
|
puts opts
|
102
125
|
exit
|
@@ -129,6 +152,10 @@ config[:jabber][:pass] ||= ask("Password for Jabber ID #{config[:jabber][:user]}
|
|
129
152
|
recipients = options[:recipients] + (config.delete(:recipients) || [])
|
130
153
|
recipients.uniq!
|
131
154
|
|
155
|
+
[:formats, :template_dir, :verbose, :debug].each { |key|
|
156
|
+
config[key] = options[key] if options[key]
|
157
|
+
}
|
158
|
+
|
132
159
|
begin
|
133
160
|
twitter2jabber = Twitter2Jabber.new(config)
|
134
161
|
|
data/lib/twitter2jabber.rb
CHANGED
@@ -25,6 +25,7 @@
|
|
25
25
|
#++
|
26
26
|
|
27
27
|
require 'time'
|
28
|
+
require 'erb'
|
28
29
|
|
29
30
|
require 'rubygems'
|
30
31
|
require 'twitter'
|
@@ -34,8 +35,17 @@ require 'twitter2jabber/version'
|
|
34
35
|
|
35
36
|
class Twitter2Jabber
|
36
37
|
|
38
|
+
MAX_LENGTH = 140
|
39
|
+
|
37
40
|
DEFAULT_PAUSE = 60
|
38
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
|
+
|
39
49
|
def self.loop(options, recipients = [], pause = nil, &block)
|
40
50
|
new(options).loop(recipients, pause, &block)
|
41
51
|
end
|
@@ -44,40 +54,65 @@ class Twitter2Jabber
|
|
44
54
|
new(options).run(recipients, &block)
|
45
55
|
end
|
46
56
|
|
47
|
-
attr_reader :twitter, :jabber, :filter
|
57
|
+
attr_reader :id, :verbose, :debug, :twitter, :jabber, :filter, :formats, :templates
|
48
58
|
|
49
59
|
def initialize(options, &block)
|
50
60
|
[:twitter, :jabber].each { |client|
|
51
61
|
raise ArgumentError, "#{client} config missing" unless options[client].is_a?(Hash)
|
52
62
|
}
|
53
63
|
|
64
|
+
@id = "#{options[:twitter][:user]} -> #{options[:jabber][:user]}"
|
65
|
+
|
66
|
+
@verbose = options[:verbose]
|
67
|
+
@debug = options[:debug]
|
68
|
+
|
54
69
|
@twitter = twitter_connect(options[:twitter])
|
55
70
|
@jabber = jabber_connect(options[:jabber])
|
56
71
|
|
57
|
-
@filter
|
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
|
+
}
|
58
80
|
end
|
59
81
|
|
60
|
-
def run(recipients = [], seen = {}, &block)
|
61
|
-
deliver_tweets(recipients, seen, &block)
|
82
|
+
def run(recipients = [], seen = {}, flag = true, &block)
|
83
|
+
deliver_tweets(recipients, seen, &block) if flag
|
62
84
|
post_messages
|
63
85
|
end
|
64
86
|
|
65
87
|
def loop(recipients = [], pause = nil, &block)
|
66
88
|
pause ||= DEFAULT_PAUSE
|
67
89
|
|
68
|
-
|
90
|
+
# jabber/twitter ratio
|
91
|
+
ratio = 10
|
92
|
+
pause /= ratio
|
69
93
|
|
70
|
-
|
71
|
-
|
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)
|
72
105
|
|
73
106
|
sleep pause
|
74
|
-
|
107
|
+
end
|
75
108
|
end
|
76
109
|
|
77
110
|
def deliver_tweets(recipients, seen = {}, &block)
|
78
111
|
get_tweets.each { |tweet|
|
79
112
|
next if seen[tweet.id]
|
80
113
|
|
114
|
+
logt tweet.id
|
115
|
+
|
81
116
|
# apply filters
|
82
117
|
next if filter && !filter[tweet]
|
83
118
|
next if block && !block[tweet]
|
@@ -85,19 +120,20 @@ class Twitter2Jabber
|
|
85
120
|
msg = format_tweet(tweet)
|
86
121
|
|
87
122
|
recipients.each { |recipient|
|
88
|
-
|
123
|
+
deliver(recipient, msg)
|
89
124
|
}
|
90
125
|
|
91
126
|
sleep 1
|
92
127
|
}
|
93
|
-
rescue Twitter::CantConnect
|
94
|
-
sleep pause
|
95
|
-
retry
|
96
128
|
end
|
97
129
|
|
98
130
|
def post_messages
|
99
131
|
jabber.received_messages { |msg|
|
100
|
-
|
132
|
+
next unless msg.type == :chat
|
133
|
+
|
134
|
+
logj msg.id
|
135
|
+
|
136
|
+
handle_command(msg.body, msg.from)
|
101
137
|
}
|
102
138
|
end
|
103
139
|
|
@@ -109,25 +145,155 @@ class Twitter2Jabber
|
|
109
145
|
|
110
146
|
# verify credentials
|
111
147
|
client.verify_credentials
|
148
|
+
|
149
|
+
logt "connected #{Time.now}"
|
150
|
+
|
112
151
|
client
|
113
152
|
rescue Twitter::TwitterError => err
|
114
153
|
raise "Can't connect to Twitter with ID '#{options[:user]}': #{err}"
|
115
154
|
end
|
116
155
|
|
117
156
|
def jabber_connect(options)
|
118
|
-
Jabber::Simple.new(options[:user], options[:pass])
|
157
|
+
client = Jabber::Simple.new(options[:user], options[:pass])
|
158
|
+
|
159
|
+
logj "connected #{Time.now}"
|
160
|
+
|
161
|
+
client
|
119
162
|
rescue Jabber::JabberError => err
|
120
163
|
raise "Can't connect to Jabber with JID '#{options[:user]}': #{err}"
|
121
164
|
end
|
122
165
|
|
123
166
|
def get_tweets
|
124
167
|
twitter.friends_timeline.sort_by { |tweet|
|
125
|
-
Time.parse(tweet.created_at)
|
168
|
+
tweet.created_at = Time.parse(tweet.created_at)
|
126
169
|
}
|
170
|
+
rescue Twitter::CantConnect
|
171
|
+
sleep pause
|
172
|
+
retry
|
127
173
|
end
|
128
174
|
|
129
175
|
def format_tweet(tweet)
|
130
|
-
|
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)
|
131
297
|
end
|
132
298
|
|
133
299
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: twitter2jabber
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jens Wille
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-08-02 00:00:00 +02:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -62,6 +62,8 @@ files:
|
|
62
62
|
- ChangeLog
|
63
63
|
- TODO
|
64
64
|
- sample/config.yaml
|
65
|
+
- sample/templates/tweet.html
|
66
|
+
- sample/templates/tweet.txt
|
65
67
|
has_rdoc: true
|
66
68
|
homepage: http://twitter2jabber.rubyforge.org/
|
67
69
|
licenses: []
|