twitter2jabber 0.7.0 → 0.8.0
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.
- checksums.yaml +4 -4
- data/ChangeLog +7 -0
- data/README +7 -7
- data/Rakefile +12 -12
- data/TODO +0 -6
- data/bin/twitter2jabber +3 -208
- data/example/config.yaml +7 -9
- data/lib/twitter2jabber.rb +35 -374
- data/lib/twitter2jabber/cli.rb +100 -0
- data/lib/twitter2jabber/jabber.rb +132 -0
- data/lib/twitter2jabber/twitter.rb +89 -0
- data/lib/twitter2jabber/version.rb +1 -1
- metadata +33 -36
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d434391b12803f08dec2790028dccd36ec5db99c
|
4
|
+
data.tar.gz: 7ab9bac4e82d981f0281f16990ef2fc7ea6d52fb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f6909849d8bd8705a47aeb84c940841344e4a41f852d349108e0c8c7edd9ae44b78179ec6ccea5cfdbbe9f37f93dd252bcf34cbd549dab9efe42587b31daf1f2
|
7
|
+
data.tar.gz: ed809ca1fce70a4479d196232d506e4585dd77197e66f6b27b781177ccd6be0abba952c4ecee7b11facf8727a75ed0c719af8070ccbe77711ed19520555227f5
|
data/ChangeLog
CHANGED
@@ -2,6 +2,13 @@
|
|
2
2
|
|
3
3
|
= Revision history for twitter2jabber
|
4
4
|
|
5
|
+
== 0.8.0 [2014-04-15]
|
6
|
+
|
7
|
+
* Dropped "write" functionality. Only forwards Twitter messages to Jabber.
|
8
|
+
* Dropped Ruby 1.8 support.
|
9
|
+
* Reduced external dependencies.
|
10
|
+
* Cut down command line options.
|
11
|
+
|
5
12
|
== 0.0.1 [2009-07-29]
|
6
13
|
|
7
14
|
* Birthday :-)
|
data/README
CHANGED
@@ -2,12 +2,13 @@
|
|
2
2
|
|
3
3
|
== VERSION
|
4
4
|
|
5
|
-
This documentation refers to twitter2jabber version 0.
|
5
|
+
This documentation refers to twitter2jabber version 0.8.0
|
6
6
|
|
7
7
|
|
8
8
|
== DESCRIPTION
|
9
9
|
|
10
|
-
|
10
|
+
Allows you to read Twitter[https://twitter.com] streams with your
|
11
|
+
favourite {Jabber client}[http://xmpp.org/xmpp-software/clients/].
|
11
12
|
|
12
13
|
Based on a script[http://da.nmilne.com/?p=353] by dkam. See also his
|
13
14
|
Jitter[http://github.com/dkam/jitter] project.
|
@@ -15,10 +16,9 @@ Jitter[http://github.com/dkam/jitter] project.
|
|
15
16
|
|
16
17
|
== LINKS
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
RubyGem:: http://rubygems.org/gems/twitter2jabber
|
19
|
+
Documentation:: https://blackwinter.github.io/twitter2jabber/
|
20
|
+
Source code:: https://github.com/blackwinter/twitter2jabber
|
21
|
+
RubyGem:: https://rubygems.org/gems/twitter2jabber
|
22
22
|
|
23
23
|
|
24
24
|
== AUTHORS
|
@@ -28,7 +28,7 @@ RubyGem:: http://rubygems.org/gems/twitter2jabber
|
|
28
28
|
|
29
29
|
== LICENSE AND COPYRIGHT
|
30
30
|
|
31
|
-
Copyright (C) 2009-
|
31
|
+
Copyright (C) 2009-2014 Jens Wille
|
32
32
|
|
33
33
|
twitter2jabber is free software: you can redistribute it and/or modify it
|
34
34
|
under the terms of the GNU Affero General Public License as published by
|
data/Rakefile
CHANGED
@@ -4,18 +4,18 @@ begin
|
|
4
4
|
require 'hen'
|
5
5
|
|
6
6
|
Hen.lay! {{
|
7
|
-
:
|
8
|
-
:
|
9
|
-
:
|
10
|
-
:
|
11
|
-
:
|
12
|
-
:
|
13
|
-
:
|
14
|
-
:
|
15
|
-
:
|
16
|
-
|
17
|
-
|
18
|
-
|
7
|
+
gem: {
|
8
|
+
name: %q{twitter2jabber},
|
9
|
+
version: Twitter2Jabber::VERSION,
|
10
|
+
summary: %q{Twitter-to-Jabber gateway.},
|
11
|
+
description: %q{Read Twitter streams through Jabber.},
|
12
|
+
author: %q{Jens Wille},
|
13
|
+
email: %q{jens.wille@gmail.com},
|
14
|
+
license: %q{AGPL-3.0},
|
15
|
+
homepage: :blackwinter,
|
16
|
+
dependencies: %w[cyclops longurl xmpp4r] << ['twitter', '>= 5.0'],
|
17
|
+
|
18
|
+
required_ruby_version: '>= 1.9.3'
|
19
19
|
}
|
20
20
|
}}
|
21
21
|
rescue LoadError => err
|
data/TODO
CHANGED
@@ -1,12 +1,6 @@
|
|
1
1
|
- include_entities=true (http://dev.twitter.com/pages/tweet_entities)
|
2
2
|
- include_rts=true (?)
|
3
|
-
- daemon control script (twitter2jabber-ctl)
|
4
|
-
- threading? (decouple Twitter and Jabber event loops)
|
5
3
|
- use streaming API? (http://apiwiki.twitter.com/Streaming-API-Documentation)
|
6
4
|
- persistent cache? (friends_timeline: since/since_id?)
|
7
|
-
- better interrupt handling (in loop mode)
|
8
|
-
- daemonize after asking for credentials (in loop mode)
|
9
|
-
- additional commands? (full twitter API?)
|
10
|
-
- refactor command handling!
|
11
5
|
- SPECS!!!
|
12
6
|
- DOCS!!
|
data/bin/twitter2jabber
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
# #
|
6
6
|
# twitter2jabber - Twitter-to-Jabber gateway. #
|
7
7
|
# #
|
8
|
-
# Copyright (C) 2009-
|
8
|
+
# Copyright (C) 2009-2014 Jens Wille #
|
9
9
|
# #
|
10
10
|
# Authors: #
|
11
11
|
# Jens Wille <jens.wille@gmail.com> #
|
@@ -26,210 +26,5 @@
|
|
26
26
|
###############################################################################
|
27
27
|
#++
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
require 'optparse'
|
32
|
-
require 'yaml'
|
33
|
-
|
34
|
-
require 'rubygems'
|
35
|
-
require 'highline/import'
|
36
|
-
|
37
|
-
$: << File.join(File.dirname(__FILE__), '..', 'lib')
|
38
|
-
|
39
|
-
require 'twitter2jabber'
|
40
|
-
|
41
|
-
USAGE = "Usage: #{$0} [-h|--help] [options]"
|
42
|
-
|
43
|
-
def ask(prompt)
|
44
|
-
super
|
45
|
-
rescue Interrupt
|
46
|
-
puts
|
47
|
-
exit
|
48
|
-
end
|
49
|
-
|
50
|
-
options = {
|
51
|
-
:config => 'config.yaml',
|
52
|
-
:consumer_token => nil,
|
53
|
-
:access_token => nil,
|
54
|
-
:jid => nil,
|
55
|
-
:recipients => [],
|
56
|
-
:formats => nil,
|
57
|
-
:template_dir => nil,
|
58
|
-
:loop => false,
|
59
|
-
:pause => nil,
|
60
|
-
:last => nil,
|
61
|
-
:wrap => nil,
|
62
|
-
:log => STDERR,
|
63
|
-
:verbose => false,
|
64
|
-
:debug => false
|
65
|
-
}
|
66
|
-
|
67
|
-
OptionParser.new { |opts|
|
68
|
-
opts.banner = USAGE
|
69
|
-
|
70
|
-
opts.separator ' '
|
71
|
-
opts.separator 'Options:'
|
72
|
-
|
73
|
-
opts.on('-c', '--config YAML', "Path to config file [Default: #{options[:config]}]") { |c|
|
74
|
-
options[:config] = c
|
75
|
-
}
|
76
|
-
|
77
|
-
opts.separator ' '
|
78
|
-
|
79
|
-
opts.on('-C', '--consumer-token TOKEN', 'Twitter consumer token') { |t|
|
80
|
-
options[:consumer_token] = t
|
81
|
-
}
|
82
|
-
|
83
|
-
opts.on('-A', '--access-token TOKEN', 'Twitter access token') { |t|
|
84
|
-
options[:access_token] = t
|
85
|
-
}
|
86
|
-
|
87
|
-
opts.on('-j', '--jid ID', 'Jabber ID') { |j|
|
88
|
-
options[:jid] = j
|
89
|
-
}
|
90
|
-
|
91
|
-
opts.separator ' '
|
92
|
-
|
93
|
-
opts.on('-r', '--recipients LIST', 'Comma-separated list of Jabber IDs') { |r|
|
94
|
-
options[:recipients] += r.split(',')
|
95
|
-
}
|
96
|
-
|
97
|
-
opts.on('-R', '--recipients-from-file FILE', 'File with one Jabber ID per line') { |r|
|
98
|
-
options[:recipients] += File.readlines(r).map { |l| l.chomp }
|
99
|
-
}
|
100
|
-
|
101
|
-
opts.separator ' '
|
102
|
-
|
103
|
-
opts.on('-f', '--formats LIST', "Comma-separated list of formats [Default: #{Twitter2Jabber::DEFAULT_FORMATS.join(',')}]") { |f|
|
104
|
-
options[:formats] = f.split(',')
|
105
|
-
}
|
106
|
-
|
107
|
-
opts.on('-t', '--template-dir PATH', 'Path to custom template directory', "[Default: #{Twitter2Jabber::DEFAULT_TEMPLATES}]") { |t|
|
108
|
-
abort "Template directory not found: #{t}" unless File.directory?(t)
|
109
|
-
options[:template_dir] = t
|
110
|
-
}
|
111
|
-
|
112
|
-
opts.separator ' '
|
113
|
-
|
114
|
-
opts.on('-l', '--loop', 'Run in a loop') {
|
115
|
-
options[:loop] = true
|
116
|
-
}
|
117
|
-
|
118
|
-
opts.on('-p', '--pause SECONDS', Integer, 'Number of seconds to pause between loops') { |p|
|
119
|
-
options[:pause] = p
|
120
|
-
}
|
121
|
-
|
122
|
-
opts.separator ' '
|
123
|
-
|
124
|
-
opts.on('-S', '--since-id ID', 'Return tweets with status IDs greater than ID') { |i|
|
125
|
-
options[:last] = i
|
126
|
-
}
|
127
|
-
|
128
|
-
opts.separator ' '
|
129
|
-
|
130
|
-
opts.on('-L', '--loop-wrap [LOG]', "Convenience switch: implies '--loop', '--verbose', and", "'--since-id'; kills existing process and logs output to LOG") { |l|
|
131
|
-
options[:wrap] = true
|
132
|
-
options[:log] = l && File.open(l, 'a')
|
133
|
-
}
|
134
|
-
|
135
|
-
opts.separator ' '
|
136
|
-
opts.separator 'Generic options:'
|
137
|
-
|
138
|
-
opts.on('-v', '--verbose', 'Be verbose') {
|
139
|
-
options[:verbose] = true
|
140
|
-
}
|
141
|
-
|
142
|
-
opts.on('-D', '--debug', "Print debug output and don't send or update anything") {
|
143
|
-
options[:debug] = true
|
144
|
-
}
|
145
|
-
|
146
|
-
opts.on('-h', '--help', 'Print this help message and exit') {
|
147
|
-
puts opts
|
148
|
-
exit
|
149
|
-
}
|
150
|
-
|
151
|
-
opts.on('--version', 'Print program version and exit') {
|
152
|
-
puts "#{File.basename($0)} v#{Twitter2Jabber::VERSION}"
|
153
|
-
exit
|
154
|
-
}
|
155
|
-
}.parse!
|
156
|
-
|
157
|
-
config = begin
|
158
|
-
YAML.load_file(options[:config])
|
159
|
-
rescue Errno::ENOENT
|
160
|
-
{}
|
161
|
-
end
|
162
|
-
|
163
|
-
# twitter config
|
164
|
-
config[:twitter] ||= {}
|
165
|
-
config[:twitter][:consumer_token] = options[:consumer_token] if options[:consumer_token]
|
166
|
-
config[:twitter][:consumer_token] ||= ask('Twitter consumer token: ')
|
167
|
-
config[:twitter][:consumer_secret] ||= ask("Consumer secret for Twitter application #{config[:twitter][:consumer_token]}: ") { |q| q.echo = false }
|
168
|
-
config[:twitter][:access_token] = options[:access_token] if options[:access_token]
|
169
|
-
config[:twitter][:access_token] ||= ask('Twitter access token: ')
|
170
|
-
config[:twitter][:access_secret] ||= ask("Access secret for Twitter user #{config[:twitter][:access_token]}: ") { |q| q.echo = false }
|
171
|
-
|
172
|
-
# jabber config
|
173
|
-
config[:jabber] ||= {}
|
174
|
-
config[:jabber][:user] = options[:jid] if options[:jid]
|
175
|
-
config[:jabber][:user] ||= ask('Jabber ID: ')
|
176
|
-
config[:jabber][:pass] ||= ask("Password for Jabber ID #{config[:jabber][:user]}: ") { |q| q.echo = false }
|
177
|
-
|
178
|
-
recipients = options[:recipients] + (config.delete(:recipients) || [])
|
179
|
-
recipients.uniq!
|
180
|
-
|
181
|
-
[:formats, :template_dir, :log, :verbose, :debug].each { |key|
|
182
|
-
config[key] = options[key] if options[key]
|
183
|
-
}
|
184
|
-
|
185
|
-
begin
|
186
|
-
if options[:wrap]
|
187
|
-
options[:loop] = true
|
188
|
-
config[:verbose] = true
|
189
|
-
|
190
|
-
case log = config[:log]
|
191
|
-
when String
|
192
|
-
config[:log] = File.open(log, 'a')
|
193
|
-
when File
|
194
|
-
log = log.path
|
195
|
-
when IO
|
196
|
-
abort "Invalid log file: #{log.tty? ? "FILENO=#{log.fileno}" : log}"
|
197
|
-
else
|
198
|
-
abort 'Log file missing!'
|
199
|
-
end
|
200
|
-
|
201
|
-
if File.readable?(log)
|
202
|
-
require 'elif'
|
203
|
-
|
204
|
-
pid = nil
|
205
|
-
|
206
|
-
Elif.foreach(log) { |line|
|
207
|
-
case line.chomp
|
208
|
-
when /\A(\d+)\z/
|
209
|
-
pid ||= $1.to_i
|
210
|
-
break if options[:last]
|
211
|
-
when / TWITTER (\d+)\z/
|
212
|
-
options[:last] ||= $1.to_i
|
213
|
-
break if pid
|
214
|
-
end
|
215
|
-
}
|
216
|
-
|
217
|
-
begin
|
218
|
-
Process.kill(:INT, pid)
|
219
|
-
sleep 1
|
220
|
-
rescue Errno::ESRCH
|
221
|
-
end if pid
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
|
-
twitter2jabber = Twitter2Jabber.new(config)
|
226
|
-
|
227
|
-
if options[:loop]
|
228
|
-
twitter2jabber.send(:log_, Process.pid)
|
229
|
-
twitter2jabber.loop(recipients, options[:pause] || config[:pause], options[:last])
|
230
|
-
else
|
231
|
-
twitter2jabber.run(recipients, options[:last])
|
232
|
-
end
|
233
|
-
rescue RuntimeError => err
|
234
|
-
abort err
|
235
|
-
end
|
29
|
+
require 'twitter2jabber/cli'
|
30
|
+
Twitter2Jabber::CLI.execute
|
data/example/config.yaml
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
---
|
2
|
+
#:log: ~/twitter2jabber.log
|
3
|
+
|
2
4
|
:twitter:
|
3
5
|
:consumer_token: c5ff16bd638c9acc6fb00197301008c5eb561f46
|
4
6
|
:consumer_secret: ae2ad9454f3af7fcb18c83969f99b20a788eddd1
|
@@ -6,12 +8,8 @@
|
|
6
8
|
:access_secret: 06c76739e3f4875a27a9e3ce36999e1af916a0e0
|
7
9
|
|
8
10
|
:jabber:
|
9
|
-
:
|
10
|
-
:
|
11
|
-
|
12
|
-
:
|
13
|
-
|
14
|
-
- two
|
15
|
-
- three
|
16
|
-
|
17
|
-
:pause: 123
|
11
|
+
#:format: txt
|
12
|
+
#:templates: ~/twitter2jabber
|
13
|
+
:recipient: joe@example.org
|
14
|
+
:username: foo@example.com
|
15
|
+
:password: bar
|
data/lib/twitter2jabber.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
# #
|
4
4
|
# twitter2jabber - Twitter-to-Jabber gateway. #
|
5
5
|
# #
|
6
|
-
# Copyright (C) 2009-
|
6
|
+
# Copyright (C) 2009-2014 Jens Wille #
|
7
7
|
# #
|
8
8
|
# Authors: #
|
9
9
|
# Jens Wille <jens.wille@gmail.com> #
|
@@ -24,404 +24,65 @@
|
|
24
24
|
###############################################################################
|
25
25
|
#++
|
26
26
|
|
27
|
-
require 'erb'
|
28
|
-
|
29
|
-
require 'rubygems'
|
30
|
-
require 'twitter'
|
31
|
-
require 'xmpp4r-simple'
|
32
|
-
require 'shorturl'
|
33
|
-
require 'longurl'
|
34
|
-
require 'nuggets/array/runiq'
|
35
|
-
|
36
|
-
require 'twitter2jabber/version'
|
37
|
-
|
38
27
|
class Twitter2Jabber
|
39
28
|
|
40
|
-
|
41
|
-
|
42
|
-
DEFAULT_PAUSE = 60
|
43
|
-
|
44
|
-
DEFAULT_FORMATS = %w[txt]
|
45
|
-
|
46
|
-
DEFAULT_TEMPLATES = File.expand_path(File.join(File.dirname(__FILE__), %w[.. example templates]))
|
47
|
-
|
48
|
-
JABBER_NS = 'http://jabber.org/protocol/xhtml-im'
|
49
|
-
XHTML_NS = 'http://www.w3.org/1999/xhtml'
|
50
|
-
|
51
|
-
def self.loop(options, recipients = [], pause = nil, last = nil, &block)
|
52
|
-
new(options).loop(recipients, pause, last, &block)
|
53
|
-
end
|
54
|
-
|
55
|
-
def self.run(options, recipients = [], last = nil, &block)
|
56
|
-
new(options).run(recipients, last, &block)
|
57
|
-
end
|
58
|
-
|
59
|
-
attr_reader :id, :verbose, :debug, :log, :twitter, :jabber, :filter, :formats, :templates, :_erb
|
60
|
-
|
61
|
-
def initialize(options, &block)
|
62
|
-
[:twitter, :jabber].each { |client|
|
63
|
-
raise ArgumentError, "#{client} config missing" unless options[client].is_a?(Hash)
|
64
|
-
}
|
65
|
-
|
66
|
-
@id = "#{options[:twitter][:consumer_token]} -> #{options[:jabber][:user]}"
|
67
|
-
|
68
|
-
@verbose = options[:verbose]
|
69
|
-
@debug = options[:debug]
|
70
|
-
@log = options[:log]
|
71
|
-
|
72
|
-
log.sync = true
|
73
|
-
logm 'HAI!'
|
74
|
-
|
75
|
-
twitter_connect(options[:twitter])
|
76
|
-
jabber_connect(options[:jabber])
|
77
|
-
|
78
|
-
@filter = options[:filter] || block
|
79
|
-
@formats = options[:formats] || DEFAULT_FORMATS
|
80
|
-
|
81
|
-
@templates = Dir[
|
82
|
-
File.join(options[:template_dir] || DEFAULT_TEMPLATES, 'tweet.*')
|
83
|
-
].inject({}) { |hash, template|
|
84
|
-
hash.update(File.extname(template).sub(/\A\./, '') => File.read(template))
|
85
|
-
}
|
86
|
-
|
87
|
-
@_erb = Hash.new { |hash, format|
|
88
|
-
template = templates[format]
|
89
|
-
hash[format] = template && ERB.new(template)
|
90
|
-
}
|
91
|
-
end
|
92
|
-
|
93
|
-
def run(recipients = [], last = nil, flag = true, &block)
|
94
|
-
last = deliver_tweets(recipients, last, &block) if flag
|
95
|
-
post_messages(recipients)
|
96
|
-
|
97
|
-
last
|
98
|
-
end
|
99
|
-
|
100
|
-
def loop(recipients = [], pause = nil, last = nil, &block)
|
101
|
-
pause ||= DEFAULT_PAUSE
|
102
|
-
|
103
|
-
i = 1
|
29
|
+
class << self
|
104
30
|
|
105
|
-
|
106
|
-
|
107
|
-
i = -1
|
108
|
-
}
|
109
|
-
|
110
|
-
while i > 0
|
111
|
-
last = run(recipients, last, i % pause == 1, &block)
|
112
|
-
|
113
|
-
sleep 1
|
114
|
-
|
115
|
-
i += 1
|
31
|
+
def run(options, since_id = nil)
|
32
|
+
new(options).connect.deliver_tweets(since_id)
|
116
33
|
end
|
117
34
|
|
118
|
-
|
119
|
-
|
120
|
-
last
|
121
|
-
end
|
122
|
-
|
123
|
-
def deliver_tweets(recipients, last = nil, &block)
|
124
|
-
get_tweets(last).each { |tweet|
|
125
|
-
logt last = tweet.id
|
126
|
-
|
127
|
-
# apply filters
|
128
|
-
next if filter && !filter[tweet]
|
129
|
-
next if block && !block[tweet]
|
130
|
-
|
131
|
-
msg = format_tweet(tweet)
|
132
|
-
|
133
|
-
recipients.each { |recipient|
|
134
|
-
deliver(recipient, msg)
|
135
|
-
}
|
136
|
-
|
137
|
-
sleep 1
|
138
|
-
}
|
139
|
-
|
140
|
-
last
|
141
|
-
end
|
142
|
-
|
143
|
-
def post_messages(recipients = [])
|
144
|
-
allowed = %r{\A(?:#{recipients.map { |r| Regexp.escape(r) }.join('|')})\z}i
|
145
|
-
|
146
|
-
jabber.received_messages { |msg|
|
147
|
-
next unless msg.type == :chat
|
148
|
-
next unless msg.from.bare.to_s =~ allowed
|
149
|
-
|
150
|
-
logj msg.id
|
151
|
-
|
152
|
-
handle_command(msg.body, msg.from)
|
153
|
-
}
|
154
|
-
end
|
155
|
-
|
156
|
-
private
|
157
|
-
|
158
|
-
def twitter_connect(options = @twitter_options)
|
159
|
-
@twitter_options = options
|
160
|
-
|
161
|
-
@twitter = Twitter::REST::Client.new(
|
162
|
-
:consumer_key => options[:consumer_token],
|
163
|
-
:consumer_secret => options[:consumer_secret],
|
164
|
-
:access_token => options[:access_token],
|
165
|
-
:access_token_secret => options[:access_secret]
|
166
|
-
)
|
167
|
-
|
168
|
-
@twitter.verify_credentials
|
169
|
-
|
170
|
-
logt 'connected'
|
171
|
-
|
172
|
-
@twitter
|
173
|
-
rescue Twitter::Error => err
|
174
|
-
raise "Can't connect to Twitter with ID '#{options[:consumer_token]}': #{err}"
|
175
|
-
end
|
176
|
-
|
177
|
-
def jabber_connect(options = @jabber_options)
|
178
|
-
@jabber_options = options
|
179
|
-
|
180
|
-
@jabber = Jabber::Simple.new(options[:user], options[:pass])
|
181
|
-
|
182
|
-
logj 'connected'
|
183
|
-
|
184
|
-
@jabber
|
185
|
-
rescue Jabber::JabberError, Errno::ETIMEDOUT => err
|
186
|
-
raise "Can't connect to Jabber with JID '#{options[:user]}': #{err}"
|
187
|
-
end
|
188
|
-
|
189
|
-
def get_tweets(last = nil)
|
190
|
-
options = {}
|
191
|
-
options[:since_id] = last if last
|
192
|
-
|
193
|
-
tweets = twitter.home_timeline(options)
|
194
|
-
return [] unless tweets.is_a?(Array)
|
195
|
-
|
196
|
-
tweets.sort_by { |tweet| tweet.created_at }
|
197
|
-
rescue Twitter::Error, Timeout::Error
|
198
|
-
[]
|
199
|
-
rescue => err
|
200
|
-
warn "#{err} (#{err.class})"
|
201
|
-
[]
|
202
|
-
end
|
203
|
-
|
204
|
-
def format_tweet(tweet)
|
205
|
-
user = tweet.user
|
206
|
-
|
207
|
-
msg = Jabber::Message.new.set_type(:chat)
|
208
|
-
|
209
|
-
formats.each { |format|
|
210
|
-
if erb = _erb[format]
|
211
|
-
msg.add_element(format_element(format, erb.result(binding)))
|
212
|
-
end
|
213
|
-
}
|
214
|
-
|
215
|
-
msg
|
216
|
-
end
|
217
|
-
|
218
|
-
# cf. <http://devblog.famundo.com/articles/2006/10/18/ruby-and-xmpp-jabber-part-3-adding-html-to-the-messages>
|
219
|
-
def format_element(format, text)
|
220
|
-
text = process_message(text)
|
221
|
-
body = REXML::Element.new('body')
|
222
|
-
|
223
|
-
case format
|
224
|
-
when 'html'
|
225
|
-
REXML::Text.new(process_html(text), false, body, true, nil, /.^/)
|
226
|
-
|
227
|
-
html = REXML::Element.new('html').add_namespace(JABBER_NS)
|
228
|
-
html.add(body.add_namespace(XHTML_NS))
|
229
|
-
html
|
230
|
-
else
|
231
|
-
REXML::Text.new(process_text(text), true, body, true, nil, /.^/)
|
232
|
-
body
|
35
|
+
def client(client)
|
36
|
+
const_get("#{client.to_s.capitalize}Client")
|
233
37
|
end
|
234
|
-
end
|
235
|
-
|
236
|
-
def process_message(text)
|
237
|
-
text.gsub(%r{https?://\S+}) { |match| LongURL.expand(match) rescue match }
|
238
|
-
end
|
239
|
-
|
240
|
-
def process_html(text)
|
241
|
-
text.gsub(/(?:\A|\W)@(\w+)/, '@<a href="https://twitter.com/\1">\1</a>').
|
242
|
-
gsub(/(?:\A|\W)#(\w+)/, '<a href="https://search.twitter.com/search?q=%23\1">#\1</a>')
|
243
|
-
end
|
244
38
|
|
245
|
-
def process_text(text)
|
246
|
-
text
|
247
39
|
end
|
248
40
|
|
249
|
-
def
|
250
|
-
|
251
|
-
|
252
|
-
deliver(from, <<-HELP) if execute
|
253
|
-
h[e[lp]]|? -- Print this help
|
254
|
-
|
255
|
-
de[bug] -- Print debug mode
|
256
|
-
de[bug] on|off -- Turn debug mode on/off
|
257
|
-
|
258
|
-
bl[ock] @USER -- Block USER
|
259
|
-
fa[v[orite]] #ID -- Add ID to favorites
|
260
|
-
|
261
|
-
bm|bookmark[s] -- List bookmarks
|
262
|
-
bm|bookmark #ID -- Bookmark ID
|
263
|
-
bm|bookmark -#ID -- Remove ID from bookmarks
|
41
|
+
def initialize(options)
|
42
|
+
@twitter = initialize_client(:twitter, options)
|
43
|
+
@jabber = initialize_client(:jabber, options)
|
264
44
|
|
265
|
-
|
266
|
-
|
267
|
-
d[m]|direct[_message] @USER [!] STATUS -- Send direct message to USER
|
45
|
+
if @debug = options[:debug] or options[:verbose]
|
46
|
+
@spec = "#{twitter.spec} -> #{jabber.spec}"
|
268
47
|
|
269
|
-
|
270
|
-
|
48
|
+
@log = options[:log] || $stderr
|
49
|
+
@log.sync = true
|
271
50
|
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
if execute
|
276
|
-
flag = $1.downcase if $1
|
277
|
-
|
278
|
-
case flag
|
279
|
-
when 'on'
|
280
|
-
@debug = true
|
281
|
-
when 'off'
|
282
|
-
@debug = false
|
283
|
-
end
|
284
|
-
|
285
|
-
deliver(from, "DEBUG = #{debug ? 'on' : 'off'}")
|
286
|
-
end
|
287
|
-
when /\Abl(?:ock)?\s+[@#]?(\w+)\z/i
|
288
|
-
twitter.block($1) if execute && !debug
|
289
|
-
when /\Afav?(?:orite)?\s+#?(\d+)\z/i
|
290
|
-
twitter.favorite_create($1) if execute && !debug
|
291
|
-
when /\A(?:bm|bookmarks?)\z/i
|
292
|
-
deliver(from, bookmarks.map { |bm|
|
293
|
-
st = begin; twitter.status(bm); rescue Twitter::Error::NotFound; end
|
294
|
-
st = "@#{st.user.screen_name}: #{st.text[0, 10]}..." if st.is_a?(Twitter::Tweet)
|
295
|
-
"#{bm} - #{st || '???'}"
|
296
|
-
}.join("\n")) if execute && !debug
|
297
|
-
when /\A(?:bm|bookmark)\s+(\d+)\z/i
|
298
|
-
if execute && !debug
|
299
|
-
bookmarks << $1
|
300
|
-
bookmarks.runiq!
|
301
|
-
end
|
302
|
-
when /\A(?:bm|bookmark)\s+-(\d+)\z/i
|
303
|
-
bookmarks.delete($1) if execute && !debug
|
304
|
-
else
|
305
|
-
command, options = nil, {}
|
306
|
-
|
307
|
-
if execute && body.sub!(/\Alen?(?:gth)?\s+/i, '')
|
308
|
-
if body = handle_command(body, from, false)
|
309
|
-
length = body.length
|
310
|
-
hint = length <= MAX_LENGTH ? 'OK' : 'TOO LONG'
|
311
|
-
|
312
|
-
deliver(from, "#{length} [#{hint}]: #{body}")
|
313
|
-
end
|
314
|
-
|
315
|
-
return
|
316
|
-
end
|
317
|
-
|
318
|
-
begin
|
319
|
-
if body.sub!(/\A(?:rt|retweet)\s+#?(\d+)(:?)(?:\s+|\z)/i, '')
|
320
|
-
id, colon = $1, $2
|
321
|
-
|
322
|
-
tweet = twitter.status(id)
|
323
|
-
|
324
|
-
if body.empty?
|
325
|
-
options[:id] = id
|
326
|
-
command = :rt
|
327
|
-
else
|
328
|
-
body << " RT @#{tweet.user.screen_name}#{colon} #{tweet.text}"
|
329
|
-
end
|
330
|
-
elsif body.sub!(/\Are(?:ply)?\s+#?(\d+)(:?)\s+/i, '')
|
331
|
-
id, colon = $1, $2
|
332
|
-
|
333
|
-
tweet = twitter.status(id)
|
334
|
-
|
335
|
-
body.insert(0, ' ') unless body.empty?
|
336
|
-
body.insert(0, "@#{tweet.user.screen_name}#{colon}")
|
337
|
-
|
338
|
-
options[:in_reply_to_status_id] = id
|
339
|
-
end
|
340
|
-
rescue Twitter::Error::NotFound
|
341
|
-
deliver(from, "TWEET NOT FOUND: #{id}")
|
342
|
-
return
|
343
|
-
end
|
344
|
-
|
345
|
-
if body.sub!(/\A(?:dm?|direct(?:_message)?)\s+[@#]?(\w+):?\s+/i, '')
|
346
|
-
options[:user] = $1
|
347
|
-
command = :dm
|
348
|
-
end
|
349
|
-
|
350
|
-
if body.sub!(/\A!(?:\s+|\z)/, '')
|
351
|
-
force = true
|
352
|
-
end
|
353
|
-
|
354
|
-
body.gsub!(/https?:\/\/\S+/) { |match|
|
355
|
-
short = ShortURL.shorten(match, :tinyurl)
|
356
|
-
short && short.length < match.length ? short : match
|
357
|
-
}
|
358
|
-
|
359
|
-
return body unless execute
|
360
|
-
|
361
|
-
if force || body.length <= MAX_LENGTH
|
362
|
-
case command
|
363
|
-
when :rt
|
364
|
-
if debug
|
365
|
-
logt "RT: #{options.inspect}", true
|
366
|
-
else
|
367
|
-
twitter.retweet(options[:id])
|
368
|
-
end
|
369
|
-
when :dm
|
370
|
-
if debug
|
371
|
-
logt "DM: #{body} (#{options.inspect})", true
|
372
|
-
else
|
373
|
-
twitter.direct_message_create(options[:user], body)
|
374
|
-
end
|
375
|
-
else
|
376
|
-
update(body, options)
|
377
|
-
end
|
378
|
-
|
379
|
-
deliver(from, "MSG SENT: #{body.inspect}, #{options.inspect}")
|
380
|
-
else
|
381
|
-
deliver(from, "MSG TOO LONG (> #{MAX_LENGTH}): #{body.inspect}")
|
382
|
-
end
|
51
|
+
require 'time'
|
52
|
+
else
|
53
|
+
define_singleton_method(:log) { |_| }
|
383
54
|
end
|
384
55
|
end
|
385
56
|
|
386
|
-
def
|
387
|
-
|
388
|
-
logj "#{recipient}: #{msg}", true
|
389
|
-
return
|
390
|
-
end
|
391
|
-
|
392
|
-
jabber.deliver(recipient, msg)
|
393
|
-
rescue => err
|
394
|
-
warn "#{err} (#{err.class})"
|
395
|
-
jabber_connect and retry if err.is_a?(Jabber::ServerDisconnected)
|
396
|
-
end
|
57
|
+
def connect
|
58
|
+
log 'Connecting...'
|
397
59
|
|
398
|
-
|
399
|
-
|
400
|
-
logt "#{msg} (#{options.inspect})", true
|
401
|
-
return
|
402
|
-
end
|
60
|
+
twitter.connect
|
61
|
+
jabber.connect
|
403
62
|
|
404
|
-
|
63
|
+
self
|
405
64
|
end
|
406
65
|
|
407
|
-
|
408
|
-
log.puts msg if verbose
|
409
|
-
end
|
66
|
+
attr_reader :twitter, :jabber, :debug
|
410
67
|
|
411
|
-
def
|
412
|
-
|
68
|
+
def deliver_tweets(since_id = nil)
|
69
|
+
twitter.tweets(since_id) { |tweet| jabber.deliver(tweet) }
|
413
70
|
end
|
414
71
|
|
415
|
-
def
|
416
|
-
|
72
|
+
def log(msg)
|
73
|
+
@log.puts "#{Time.now.xmlschema} [#{@spec}] #{msg}"
|
417
74
|
end
|
418
75
|
|
419
|
-
|
420
|
-
logm("JABBER #{msg}", verbose)
|
421
|
-
end
|
76
|
+
private
|
422
77
|
|
423
|
-
def
|
424
|
-
|
78
|
+
def initialize_client(client, options)
|
79
|
+
(config = options[client]).is_a?(Hash) ?
|
80
|
+
self.class.client(client).new(self, config) :
|
81
|
+
raise(ArgumentError, "#{client} config missing")
|
425
82
|
end
|
426
83
|
|
427
84
|
end
|
85
|
+
|
86
|
+
require_relative 'twitter2jabber/version'
|
87
|
+
require_relative 'twitter2jabber/twitter'
|
88
|
+
require_relative 'twitter2jabber/jabber'
|
@@ -0,0 +1,100 @@
|
|
1
|
+
#--
|
2
|
+
###############################################################################
|
3
|
+
# #
|
4
|
+
# twitter2jabber - Twitter-to-Jabber gateway. #
|
5
|
+
# #
|
6
|
+
# Copyright (C) 2009-2014 Jens Wille #
|
7
|
+
# #
|
8
|
+
# Authors: #
|
9
|
+
# Jens Wille <jens.wille@gmail.com> #
|
10
|
+
# #
|
11
|
+
# twitter2jabber is free software; you can redistribute it and/or modify it #
|
12
|
+
# under the terms of the GNU Affero General Public License as published by #
|
13
|
+
# the Free Software Foundation; either version 3 of the License, or (at your #
|
14
|
+
# option) 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 Affero General Public #
|
19
|
+
# License for more details. #
|
20
|
+
# #
|
21
|
+
# You should have received a copy of the GNU Affero General Public License #
|
22
|
+
# along with twitter2jabber. If not, see <http://www.gnu.org/licenses/>. #
|
23
|
+
# #
|
24
|
+
###############################################################################
|
25
|
+
#++
|
26
|
+
|
27
|
+
require 'cyclops'
|
28
|
+
require 'twitter2jabber'
|
29
|
+
|
30
|
+
class Twitter2Jabber
|
31
|
+
|
32
|
+
class CLI < Cyclops
|
33
|
+
|
34
|
+
class << self
|
35
|
+
|
36
|
+
def defaults
|
37
|
+
super.merge(
|
38
|
+
config: 'config.yaml',
|
39
|
+
since_id: nil,
|
40
|
+
verbose: false,
|
41
|
+
debug: false
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
def extract_since_id(log)
|
46
|
+
return unless File.readable?(log)
|
47
|
+
|
48
|
+
id, re = nil, /\bTWITTER\s+(\d+)\Z/
|
49
|
+
|
50
|
+
File.foreach(log) { |line|
|
51
|
+
id = $1 if line =~ re
|
52
|
+
}
|
53
|
+
|
54
|
+
id.to_i if id
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
def run(arguments)
|
60
|
+
if log = options[:log]
|
61
|
+
options[:log] = File.open(log, 'a')
|
62
|
+
options[:since_id] ||= self.class.extract_since_id(log)
|
63
|
+
end
|
64
|
+
|
65
|
+
Twitter2Jabber.run(options, options.delete(:since_id))
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def parse_options(arguments)
|
71
|
+
super
|
72
|
+
|
73
|
+
options[:log] &&= File.expand_path(options[:log])
|
74
|
+
|
75
|
+
t = options[:twitter] ||= {}
|
76
|
+
t[:consumer_token] ||= ask('Twitter consumer token: ')
|
77
|
+
t[:consumer_secret] ||= askpass("Consumer secret for Twitter application #{t[:consumer_token]}: ")
|
78
|
+
t[:access_token] ||= ask('Twitter access token: ')
|
79
|
+
t[:access_secret] ||= askpass("Access secret for Twitter user #{t[:access_token]}: ")
|
80
|
+
|
81
|
+
j = options[:jabber] ||= {}
|
82
|
+
j[:username] ||= ask('Jabber ID: ')
|
83
|
+
j[:password] ||= askpass("Password for Jabber ID #{j[:username]}: ")
|
84
|
+
end
|
85
|
+
|
86
|
+
def opts(opts)
|
87
|
+
opts.option(:since_id__ID, Integer, 'Return tweets with status IDs greater than ID')
|
88
|
+
|
89
|
+
opts.separator
|
90
|
+
|
91
|
+
opts.option(:log__FILE, 'Path to log file [Default: STDERR]')
|
92
|
+
end
|
93
|
+
|
94
|
+
def debug_message
|
95
|
+
"don't send any messages"
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
#--
|
2
|
+
###############################################################################
|
3
|
+
# #
|
4
|
+
# twitter2jabber - Twitter-to-Jabber gateway. #
|
5
|
+
# #
|
6
|
+
# Copyright (C) 2009-2014 Jens Wille #
|
7
|
+
# #
|
8
|
+
# Authors: #
|
9
|
+
# Jens Wille <jens.wille@gmail.com> #
|
10
|
+
# #
|
11
|
+
# twitter2jabber is free software; you can redistribute it and/or modify it #
|
12
|
+
# under the terms of the GNU Affero General Public License as published by #
|
13
|
+
# the Free Software Foundation; either version 3 of the License, or (at your #
|
14
|
+
# option) 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 Affero General Public #
|
19
|
+
# License for more details. #
|
20
|
+
# #
|
21
|
+
# You should have received a copy of the GNU Affero General Public License #
|
22
|
+
# along with twitter2jabber. If not, see <http://www.gnu.org/licenses/>. #
|
23
|
+
# #
|
24
|
+
###############################################################################
|
25
|
+
#++
|
26
|
+
|
27
|
+
require 'erb'
|
28
|
+
require 'xmpp4r'
|
29
|
+
|
30
|
+
class Twitter2Jabber
|
31
|
+
|
32
|
+
class JabberClient
|
33
|
+
|
34
|
+
DEFAULT_TEMPLATES = File.expand_path('../../../example/templates', __FILE__)
|
35
|
+
|
36
|
+
DEFAULT_FORMAT = 'txt'
|
37
|
+
|
38
|
+
JABBER_NS = 'http://jabber.org/protocol/xhtml-im'
|
39
|
+
XHTML_NS = 'http://www.w3.org/1999/xhtml'
|
40
|
+
|
41
|
+
def initialize(gw, config)
|
42
|
+
@gw, @config, @spec = gw, config, config[:username]
|
43
|
+
|
44
|
+
@format = config.fetch(:format, DEFAULT_FORMAT).to_s
|
45
|
+
|
46
|
+
if File.readable?(template = File.expand_path(File.join(
|
47
|
+
config[:templates] || DEFAULT_TEMPLATES, "tweet.#{@format}")))
|
48
|
+
|
49
|
+
@erb = ERB.new(File.read(template))
|
50
|
+
else
|
51
|
+
raise ArgumentError, "format not supported: #{@format}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
attr_reader :config, :spec
|
56
|
+
|
57
|
+
def client
|
58
|
+
@client ||= Jabber::Client.new(spec).tap { |client|
|
59
|
+
client.connect
|
60
|
+
client.auth(config[:password])
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
def connect
|
65
|
+
deliver(Jabber::Presence.new(nil, 'Available'), nil)
|
66
|
+
log 'connected'
|
67
|
+
rescue Jabber::JabberError, Errno::ETIMEDOUT => err
|
68
|
+
raise "Can't connect to Jabber with JID '#{spec}': #{err}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def format(tweet)
|
72
|
+
user = tweet.user
|
73
|
+
text = @erb.result(binding)
|
74
|
+
|
75
|
+
msg = Jabber::Message.new.set_type(:chat)
|
76
|
+
msg.add_element(format_element(text))
|
77
|
+
msg
|
78
|
+
end
|
79
|
+
|
80
|
+
def deliver(msg, recipient = config[:recipient])
|
81
|
+
msg = format(msg) unless msg.is_a?(Jabber::XMPPStanza)
|
82
|
+
msg.to = Jabber::JID.new(recipient).strip if recipient
|
83
|
+
|
84
|
+
@gw.debug ? log("#{recipient}: #{msg}") : send_msg(msg)
|
85
|
+
rescue => err
|
86
|
+
warn "#{err} (#{err.class})"
|
87
|
+
retry if err.is_a?(Jabber::ServerDisconnected)
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
# cf. <http://devblog.famundo.com/articles/2006/10/18/ruby-and-xmpp-jabber-part-3-adding-html-to-the-messages>
|
93
|
+
def format_element(text)
|
94
|
+
twitter = @gw.twitter
|
95
|
+
|
96
|
+
text, body = twitter.process_message(text), REXML::Element.new('body')
|
97
|
+
|
98
|
+
if @format == 'html'
|
99
|
+
REXML::Text.new(twitter.process_html(text), false, body, true, nil, /.^/)
|
100
|
+
|
101
|
+
html = REXML::Element.new('html').add_namespace(JABBER_NS)
|
102
|
+
html.add(body.add_namespace(XHTML_NS))
|
103
|
+
html
|
104
|
+
else
|
105
|
+
REXML::Text.new(twitter.process_text(text), true, body, true, nil, /.^/)
|
106
|
+
body
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def send_msg(msg, attempts = 0)
|
111
|
+
attempts += 1
|
112
|
+
client.send(msg)
|
113
|
+
rescue Errno::EPIPE, IOError
|
114
|
+
raise if attempts > 3
|
115
|
+
|
116
|
+
begin
|
117
|
+
@client.close
|
118
|
+
rescue Errno::EPIPE, IOError
|
119
|
+
end
|
120
|
+
|
121
|
+
@client = nil
|
122
|
+
sleep 1
|
123
|
+
retry
|
124
|
+
end
|
125
|
+
|
126
|
+
def log(msg)
|
127
|
+
@gw.log("JABBER #{msg}")
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
#--
|
2
|
+
###############################################################################
|
3
|
+
# #
|
4
|
+
# twitter2jabber - Twitter-to-Jabber gateway. #
|
5
|
+
# #
|
6
|
+
# Copyright (C) 2009-2014 Jens Wille #
|
7
|
+
# #
|
8
|
+
# Authors: #
|
9
|
+
# Jens Wille <jens.wille@gmail.com> #
|
10
|
+
# #
|
11
|
+
# twitter2jabber is free software; you can redistribute it and/or modify it #
|
12
|
+
# under the terms of the GNU Affero General Public License as published by #
|
13
|
+
# the Free Software Foundation; either version 3 of the License, or (at your #
|
14
|
+
# option) 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 Affero General Public #
|
19
|
+
# License for more details. #
|
20
|
+
# #
|
21
|
+
# You should have received a copy of the GNU Affero General Public License #
|
22
|
+
# along with twitter2jabber. If not, see <http://www.gnu.org/licenses/>. #
|
23
|
+
# #
|
24
|
+
###############################################################################
|
25
|
+
#++
|
26
|
+
|
27
|
+
require 'twitter'
|
28
|
+
require 'longurl'
|
29
|
+
|
30
|
+
class Twitter2Jabber
|
31
|
+
|
32
|
+
class TwitterClient
|
33
|
+
|
34
|
+
def initialize(gw, config)
|
35
|
+
@gw, @config, @client = gw, config, Twitter::REST::Client.new(
|
36
|
+
consumer_key: @spec = config[:consumer_token],
|
37
|
+
consumer_secret: config[:consumer_secret],
|
38
|
+
access_token: config[:access_token],
|
39
|
+
access_token_secret: config[:access_secret]
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_reader :config, :spec, :client
|
44
|
+
|
45
|
+
def connect
|
46
|
+
client.verify_credentials
|
47
|
+
log 'connected'
|
48
|
+
rescue Twitter::Error => err
|
49
|
+
raise "Can't connect to Twitter with ID '#{spec}': #{err}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def tweets(since_id = nil)
|
53
|
+
tweets = client.home_timeline(since_id: since_id)
|
54
|
+
return unless tweets.is_a?(Array)
|
55
|
+
|
56
|
+
tweets.sort_by { |tweet| tweet.created_at }.each { |tweet|
|
57
|
+
log since_id = tweet.id
|
58
|
+
yield tweet
|
59
|
+
sleep 1
|
60
|
+
}
|
61
|
+
|
62
|
+
since_id
|
63
|
+
rescue Twitter::Error, Timeout::Error
|
64
|
+
rescue => err
|
65
|
+
warn "#{err} (#{err.class})"
|
66
|
+
end
|
67
|
+
|
68
|
+
def process_message(text)
|
69
|
+
text.gsub(%r{https?://\S+}) { |match| LongURL.expand(match) rescue match }
|
70
|
+
end
|
71
|
+
|
72
|
+
def process_html(text)
|
73
|
+
text.gsub(/(?:\A|\W)@(\w+)/, '@<a href="https://twitter.com/\1">\1</a>')
|
74
|
+
.gsub(/(?:\A|\W)#(\w+)/, '<a href="https://search.twitter.com/search?q=%23\1">#\1</a>')
|
75
|
+
end
|
76
|
+
|
77
|
+
def process_text(text)
|
78
|
+
text
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def log(msg)
|
84
|
+
@gw.log("TWITTER #{msg}")
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
metadata
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: twitter2jabber
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jens Wille
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-04-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: cyclops
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
@@ -25,7 +25,7 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: longurl
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
@@ -39,7 +39,7 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: xmpp4r
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
@@ -53,27 +53,27 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: twitter
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
61
|
+
version: '5.0'
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
68
|
+
version: '5.0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: hen
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '0'
|
76
|
-
type: :
|
76
|
+
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
@@ -81,34 +81,20 @@ dependencies:
|
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: rake
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - ">="
|
88
88
|
- !ruby/object:Gem::Version
|
89
89
|
version: '0'
|
90
|
-
type: :
|
90
|
+
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
|
-
|
98
|
-
name: twitter
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
100
|
-
requirements:
|
101
|
-
- - ">="
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '5.0'
|
104
|
-
type: :runtime
|
105
|
-
prerelease: false
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
107
|
-
requirements:
|
108
|
-
- - ">="
|
109
|
-
- !ruby/object:Gem::Version
|
110
|
-
version: '5.0'
|
111
|
-
description: Twitter-to-Jabber gateway.
|
97
|
+
description: Read Twitter streams through Jabber.
|
112
98
|
email: jens.wille@gmail.com
|
113
99
|
executables:
|
114
100
|
- twitter2jabber
|
@@ -118,29 +104,40 @@ extra_rdoc_files:
|
|
118
104
|
- COPYING
|
119
105
|
- ChangeLog
|
120
106
|
files:
|
121
|
-
- lib/twitter2jabber.rb
|
122
|
-
- lib/twitter2jabber/version.rb
|
123
|
-
- bin/twitter2jabber
|
124
107
|
- COPYING
|
125
108
|
- ChangeLog
|
126
109
|
- README
|
127
110
|
- Rakefile
|
128
111
|
- TODO
|
112
|
+
- bin/twitter2jabber
|
129
113
|
- example/config.yaml
|
130
114
|
- example/templates/tweet.html
|
131
115
|
- example/templates/tweet.txt
|
116
|
+
- lib/twitter2jabber.rb
|
117
|
+
- lib/twitter2jabber/cli.rb
|
118
|
+
- lib/twitter2jabber/jabber.rb
|
119
|
+
- lib/twitter2jabber/twitter.rb
|
120
|
+
- lib/twitter2jabber/version.rb
|
132
121
|
homepage: http://github.com/blackwinter/twitter2jabber
|
133
122
|
licenses:
|
134
|
-
- AGPL
|
123
|
+
- AGPL-3.0
|
135
124
|
metadata: {}
|
136
|
-
post_install_message:
|
125
|
+
post_install_message: |2+
|
126
|
+
|
127
|
+
twitter2jabber-0.8.0 [2014-04-15]:
|
128
|
+
|
129
|
+
* Dropped "write" functionality. Only forwards Twitter messages to Jabber.
|
130
|
+
* Dropped Ruby 1.8 support.
|
131
|
+
* Reduced external dependencies.
|
132
|
+
* Cut down command line options.
|
133
|
+
|
137
134
|
rdoc_options:
|
135
|
+
- "--title"
|
136
|
+
- twitter2jabber Application documentation (v0.8.0)
|
138
137
|
- "--charset"
|
139
138
|
- UTF-8
|
140
139
|
- "--line-numbers"
|
141
140
|
- "--all"
|
142
|
-
- "--title"
|
143
|
-
- twitter2jabber Application documentation (v0.7.0)
|
144
141
|
- "--main"
|
145
142
|
- README
|
146
143
|
require_paths:
|
@@ -149,7 +146,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
149
146
|
requirements:
|
150
147
|
- - ">="
|
151
148
|
- !ruby/object:Gem::Version
|
152
|
-
version:
|
149
|
+
version: 1.9.3
|
153
150
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
154
151
|
requirements:
|
155
152
|
- - ">="
|
@@ -157,7 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
157
154
|
version: '0'
|
158
155
|
requirements: []
|
159
156
|
rubyforge_project:
|
160
|
-
rubygems_version: 2.
|
157
|
+
rubygems_version: 2.2.2
|
161
158
|
signing_key:
|
162
159
|
specification_version: 4
|
163
160
|
summary: Twitter-to-Jabber gateway.
|