net-irc 0.0.8 → 0.0.9
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/ChangeLog +9 -0
- data/Rakefile +10 -19
- data/examples/2ch.rb +225 -0
- data/examples/2ig.rb +267 -0
- data/examples/echo_bot.rb +1 -1
- data/examples/gmail.rb +2 -2
- data/examples/hatena-star-stream.rb +10 -4
- data/examples/hig.rb +9 -4
- data/examples/iig.rb +3 -3
- data/examples/ircd.rb +1 -1
- data/examples/lig.rb +551 -0
- data/examples/lingr.rb +327 -0
- data/examples/mixi.rb +2 -2
- data/examples/sig.rb +2 -2
- data/examples/tig.rb +552 -386
- data/examples/wig.rb +11 -4
- data/lib/net/irc.rb +1 -1
- data/lib/net/irc/server.rb +1 -0
- data/spec/channel_manager_spec.rb +4 -1
- data/spec/net-irc_spec.rb +4 -2
- metadata +12 -11
data/examples/lingr.rb
ADDED
@@ -0,0 +1,327 @@
|
|
1
|
+
# Ruby client for the Lingr[http://www.lingr.com] API. For more details and tutorials, see the
|
2
|
+
# {Lingr API Reference}[http://wiki.lingr.com/dev/show/API+Reference] pages on the {Lingr Developer Wiki}[http://wiki.lingr.com].
|
3
|
+
#
|
4
|
+
# All methods return a hash with two keys:
|
5
|
+
# * :succeeded - <tt>true</tt> if the method succeeded, <tt>false</tt> otherwise
|
6
|
+
# * :response - a Hash version of the response document received from the server
|
7
|
+
#
|
8
|
+
# = api_client.rb
|
9
|
+
#
|
10
|
+
# Lingr API client
|
11
|
+
#
|
12
|
+
#
|
13
|
+
# Original written by Lingr.
|
14
|
+
# Modified by cho45 <cho45@lowreal.net>
|
15
|
+
# * Use json gem instead of gsub/eval.
|
16
|
+
# * Raise APIError when api fails.
|
17
|
+
# * Rename class name to Lingr::Client.
|
18
|
+
|
19
|
+
$KCODE = 'u' # used by json
|
20
|
+
require "rubygems"
|
21
|
+
require "net/http"
|
22
|
+
require "json"
|
23
|
+
require "uri"
|
24
|
+
require "timeout"
|
25
|
+
|
26
|
+
module Lingr
|
27
|
+
class Client
|
28
|
+
class ClientError < StandardError; end
|
29
|
+
class APIError < ClientError
|
30
|
+
def initialize(error)
|
31
|
+
@error = error || {
|
32
|
+
"message" => "socket error",
|
33
|
+
"code" => 0,
|
34
|
+
}
|
35
|
+
super(@error["message"])
|
36
|
+
end
|
37
|
+
|
38
|
+
def code
|
39
|
+
@error["code"]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_accessor :api_key
|
44
|
+
# 0 = quiet, 1 = some debug info, 2 = more debug info
|
45
|
+
attr_accessor :verbosity
|
46
|
+
attr_accessor :session
|
47
|
+
attr_accessor :timeout
|
48
|
+
|
49
|
+
def initialize(api_key, verbosity=0, hostname='www.lingr.com')
|
50
|
+
@api_key = api_key
|
51
|
+
@host = hostname
|
52
|
+
@verbosity = verbosity
|
53
|
+
@timeout = 60
|
54
|
+
end
|
55
|
+
|
56
|
+
# Create a new API session
|
57
|
+
#
|
58
|
+
def create_session(client_type='automaton')
|
59
|
+
if @session
|
60
|
+
@error_info = nil
|
61
|
+
raise ClientError, "already in a session"
|
62
|
+
end
|
63
|
+
|
64
|
+
ret = do_api :post, 'session/create', { :api_key => @api_key, :client_type => client_type }, false
|
65
|
+
@session = ret["session"]
|
66
|
+
ret
|
67
|
+
end
|
68
|
+
|
69
|
+
# Verify a session id. If no session id is passed, verifies the current session id for this ApiClient
|
70
|
+
#
|
71
|
+
def verify_session(session_id=nil)
|
72
|
+
do_api :get, 'session/verify', { :session => session_id || @session }, false
|
73
|
+
end
|
74
|
+
|
75
|
+
# Destroy the current API session
|
76
|
+
#
|
77
|
+
def destroy_session
|
78
|
+
ret = do_api :post, 'session/destroy', { :session => @session }
|
79
|
+
@session = nil
|
80
|
+
ret
|
81
|
+
end
|
82
|
+
|
83
|
+
# Get a list of the currently hot rooms
|
84
|
+
#
|
85
|
+
def get_hot_rooms(count=nil)
|
86
|
+
do_api :get, 'explore/get_hot_rooms', { :api_key => @api_key }.merge(count ? { :count => count} : {}), false
|
87
|
+
end
|
88
|
+
|
89
|
+
# Get a list of the newest rooms
|
90
|
+
#
|
91
|
+
def get_new_rooms(count=nil)
|
92
|
+
do_api :get, 'explore/get_new_rooms', { :api_key => @api_key }.merge(count ? { :count => count} : {}), false
|
93
|
+
end
|
94
|
+
|
95
|
+
# Get a list of the currently hot tags
|
96
|
+
#
|
97
|
+
def get_hot_tags(count=nil)
|
98
|
+
do_api :get, 'explore/get_hot_tags', { :api_key => @api_key }.merge(count ? { :count => count} : {}), false
|
99
|
+
end
|
100
|
+
|
101
|
+
# Get a list of all tags
|
102
|
+
#
|
103
|
+
def get_all_tags(count=nil)
|
104
|
+
do_api :get, 'explore/get_all_tags', { :api_key => @api_key }.merge(count ? { :count => count} : {}), false
|
105
|
+
end
|
106
|
+
|
107
|
+
# Search room name, description, and tags for keywords. Keywords can be a String or an Array.
|
108
|
+
#
|
109
|
+
def search(keywords)
|
110
|
+
do_api :get, 'explore/search', { :api_key => @api_key, :q => keywords.is_a?(Array) ? keywords.join(',') : keywords }, false
|
111
|
+
end
|
112
|
+
|
113
|
+
# Search room tags. Tagnames can be a String or an Array.
|
114
|
+
#
|
115
|
+
def search_tags(tagnames)
|
116
|
+
do_api :get, 'explore/search_tags', { :api_key => @api_key, :q => tagnames.is_a?(Array) ? tagnames.join(',') : tagnames }, false
|
117
|
+
end
|
118
|
+
|
119
|
+
# Search archives. If room_id is non-nil, the search is limited to the archives of that room.
|
120
|
+
#
|
121
|
+
def search_archives(query, room_id=nil)
|
122
|
+
params = { :api_key => @api_key, :q => query }
|
123
|
+
params.merge!({ :id => room_id }) if room_id
|
124
|
+
do_api :get, 'explore/search_archives', params, false
|
125
|
+
end
|
126
|
+
|
127
|
+
# Authenticate a user within the current API session
|
128
|
+
#
|
129
|
+
def login(email, password)
|
130
|
+
do_api :post, 'auth/login', { :session => @session, :email => email, :password => password }
|
131
|
+
end
|
132
|
+
|
133
|
+
# Log out the currently-authenticated user in the session, if any
|
134
|
+
#
|
135
|
+
def logout
|
136
|
+
do_api :post, 'auth/logout', { :session => @session }
|
137
|
+
end
|
138
|
+
|
139
|
+
# Get information about the currently-authenticated user
|
140
|
+
#
|
141
|
+
def get_user_info
|
142
|
+
do_api :get, 'user/get_info', { :session => @session }
|
143
|
+
end
|
144
|
+
|
145
|
+
# Start observing the currently-authenticated user
|
146
|
+
#
|
147
|
+
def start_observing_user
|
148
|
+
do_api :post, 'user/start_observing', { :session => @session }
|
149
|
+
end
|
150
|
+
|
151
|
+
# Observe the currently-authenticated user, watching for profile changes
|
152
|
+
#
|
153
|
+
def observe_user(ticket, counter)
|
154
|
+
do_api :get, 'user/observe', { :session => @session, :ticket => ticket, :counter => counter }
|
155
|
+
end
|
156
|
+
|
157
|
+
# Stop observing the currently-authenticated user
|
158
|
+
#
|
159
|
+
def stop_observing_user(ticket)
|
160
|
+
do_api :post, 'user/stop_observing', { :session => @session, :ticket =>ticket }
|
161
|
+
end
|
162
|
+
|
163
|
+
# Get information about a chatroom, including room description, current occupants, recent messages, etc.
|
164
|
+
#
|
165
|
+
def get_room_info(room_id, counter=nil, password=nil)
|
166
|
+
params = { :api_key => @api_key, :id => room_id }
|
167
|
+
params.merge!({ :counter => counter }) if counter
|
168
|
+
params.merge!({ :password => password }) if password
|
169
|
+
do_api :get, 'room/get_info', params, false
|
170
|
+
end
|
171
|
+
|
172
|
+
# Create a chatroom
|
173
|
+
#
|
174
|
+
# options is a Hash containing any of the parameters allowed for room.create. If the :image key is present
|
175
|
+
# in options, its value must be a hash with the keys :filename, :mime_type, and :io
|
176
|
+
#
|
177
|
+
def create_room(options)
|
178
|
+
do_api :post, 'room/create', options.merge({ :session => @session })
|
179
|
+
end
|
180
|
+
|
181
|
+
# Change the settings for a chatroom
|
182
|
+
#
|
183
|
+
# options is a Hash containing any of the parameters allowed for room.create. If the :image key is present
|
184
|
+
# in options, its value must be a hash with the keys :filename, :mime_type, and :io. To change the id for
|
185
|
+
# a room, use the key :new_id
|
186
|
+
#
|
187
|
+
def change_settings(room_id, options)
|
188
|
+
do_api :post, 'room/change_settings', options.merge({ :session => @session })
|
189
|
+
end
|
190
|
+
|
191
|
+
# Delete a chatroom
|
192
|
+
#
|
193
|
+
def delete_room(room_id)
|
194
|
+
do_api :post, 'room/delete', { :id => room_id, :session => @session }
|
195
|
+
end
|
196
|
+
|
197
|
+
# Enter a chatroom
|
198
|
+
#
|
199
|
+
def enter_room(room_id, nickname=nil, password=nil, idempotent=false)
|
200
|
+
params = { :session => @session, :id => room_id }
|
201
|
+
params.merge!({ :nickname => nickname }) if nickname
|
202
|
+
params.merge!({ :password => password }) if password
|
203
|
+
params.merge!({ :idempotent => 'true' }) if idempotent
|
204
|
+
do_api :post, 'room/enter', params
|
205
|
+
end
|
206
|
+
|
207
|
+
# Poll for messages in a chatroom
|
208
|
+
#
|
209
|
+
def get_messages(ticket, counter, user_messages_only=false)
|
210
|
+
do_api :get, 'room/get_messages', { :session => @session, :ticket => ticket, :counter => counter, :user_messages_only => user_messages_only }
|
211
|
+
end
|
212
|
+
|
213
|
+
# Observe a chatroom, waiting for events to occur in the room
|
214
|
+
#
|
215
|
+
def observe_room(ticket, counter)
|
216
|
+
do_api :get, 'room/observe', { :session => @session, :ticket => ticket, :counter => counter }
|
217
|
+
end
|
218
|
+
|
219
|
+
# Set your nickname in a chatroom
|
220
|
+
#
|
221
|
+
def set_nickname(ticket, nickname)
|
222
|
+
do_api :post, 'room/set_nickname', { :session => @session, :ticket => ticket, :nickname => nickname }
|
223
|
+
end
|
224
|
+
|
225
|
+
# Say something in a chatroom. If target_occupant_id is not nil, a private message
|
226
|
+
# is sent to the indicated occupant.
|
227
|
+
#
|
228
|
+
def say(ticket, msg, target_occupant_id = nil)
|
229
|
+
params = { :session => @session, :ticket => ticket, :message => msg }
|
230
|
+
params.merge!({ :occupant_id => target_occupant_id}) if target_occupant_id
|
231
|
+
do_api :post, 'room/say', params
|
232
|
+
end
|
233
|
+
|
234
|
+
# Exit a chatroom
|
235
|
+
#
|
236
|
+
def exit_room(ticket)
|
237
|
+
do_api :post, 'room/exit', { :session => @session, :ticket => ticket }
|
238
|
+
end
|
239
|
+
|
240
|
+
private
|
241
|
+
|
242
|
+
def do_api(method, path, parameters, require_session=true)
|
243
|
+
if require_session and !@session
|
244
|
+
raise ClientError, "not in a session"
|
245
|
+
end
|
246
|
+
|
247
|
+
response = Timeout.timeout(@timeout) {
|
248
|
+
JSON.parse(self.send(method, url_for(path), parameters.merge({ :format => 'json' })))
|
249
|
+
}
|
250
|
+
|
251
|
+
unless success?(response)
|
252
|
+
raise APIError, response["error"]
|
253
|
+
end
|
254
|
+
|
255
|
+
response
|
256
|
+
end
|
257
|
+
|
258
|
+
def url_for(method)
|
259
|
+
"http://#{@host}/#{@@PATH_BASE}#{method}"
|
260
|
+
end
|
261
|
+
|
262
|
+
def get(url, params)
|
263
|
+
uri = URI.parse(url)
|
264
|
+
path = uri.path
|
265
|
+
q = params.inject("?") {|s, p| s << "#{p[0].to_s}=#{URI.encode(p[1].to_s, /./)}&"}.chop
|
266
|
+
path << q if q.length > 0
|
267
|
+
|
268
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
269
|
+
http.read_timeout = @timeout
|
270
|
+
req = Net::HTTP::Get.new(path)
|
271
|
+
req.basic_auth(uri.user, uri.password) if uri.user
|
272
|
+
parse_result http.request(req)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
def post(url, params)
|
277
|
+
if !params.find {|p| p[1].is_a?(Hash)}
|
278
|
+
params = params.inject({}){|hash,(k,v)| hash[k.to_s] = v; hash}
|
279
|
+
parse_result Net::HTTP.post_form(URI.parse(url), params)
|
280
|
+
else
|
281
|
+
boundary = 'lingr-api-client' + (0x1000000 + rand(0x1000000).to_s(16))
|
282
|
+
|
283
|
+
query = params.collect { |p|
|
284
|
+
ret = ["--#{boundary}"]
|
285
|
+
|
286
|
+
if p[1].is_a?(Hash)
|
287
|
+
ret << "Content-Disposition: form-data; name=\"#{URI.encode(p[0].to_s)}\"; filename=\"#{p[1][:filename]}\""
|
288
|
+
ret << "Content-Transfer-Encoding: binary"
|
289
|
+
ret << "Content-Type: #{p[1][:mime_type]}"
|
290
|
+
ret << ""
|
291
|
+
ret << p[1][:io].read
|
292
|
+
else
|
293
|
+
ret << "Content-Disposition: form-data; name=\"#{URI.encode(p[0].to_s)}\""
|
294
|
+
ret << ""
|
295
|
+
ret << p[1]
|
296
|
+
end
|
297
|
+
|
298
|
+
ret.join("\r\n")
|
299
|
+
}.join('') + "--#{boundary}--\r\n"
|
300
|
+
|
301
|
+
uri = URI.parse(url)
|
302
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
303
|
+
http.read_timeout = @timeout
|
304
|
+
parse_result http.post2(uri.path, query, "Content-Type" => "multipart/form-data; boundary=#{boundary}")
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def parse_result(result)
|
310
|
+
return nil if !result || result.code != '200' || (!result['Content-Type'] || result['Content-Type'].index('text/javascript') != 0)
|
311
|
+
# puts
|
312
|
+
# puts
|
313
|
+
# puts result.body
|
314
|
+
# puts
|
315
|
+
# puts
|
316
|
+
result.body
|
317
|
+
end
|
318
|
+
|
319
|
+
def success?(response)
|
320
|
+
return false if !response
|
321
|
+
response["status"] and response["status"] == 'ok'
|
322
|
+
end
|
323
|
+
|
324
|
+
|
325
|
+
@@PATH_BASE = 'api/'
|
326
|
+
end
|
327
|
+
end
|
data/examples/mixi.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
# vim:
|
2
|
+
# vim:encoding=UTF-8:
|
3
3
|
=begin
|
4
4
|
|
5
5
|
|
@@ -12,7 +12,7 @@ Ruby's by cho45
|
|
12
12
|
$LOAD_PATH << "lib"
|
13
13
|
$LOAD_PATH << "../lib"
|
14
14
|
|
15
|
-
$KCODE = "u"
|
15
|
+
$KCODE = "u" unless defined? ::Encoding # json use this
|
16
16
|
|
17
17
|
require "rubygems"
|
18
18
|
require "json"
|
data/examples/sig.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
# vim:
|
2
|
+
# vim:encoding=UTF-8:
|
3
3
|
=begin
|
4
4
|
# sig.rb
|
5
5
|
|
@@ -17,7 +17,7 @@ ServerLog IRC Gateway
|
|
17
17
|
$LOAD_PATH << "lib"
|
18
18
|
$LOAD_PATH << "../lib"
|
19
19
|
|
20
|
-
$KCODE = "u"
|
20
|
+
$KCODE = "u" unless defined? ::Encoding
|
21
21
|
|
22
22
|
require "rubygems"
|
23
23
|
require "net/irc"
|
data/examples/tig.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
# vim:
|
3
|
-
$KCODE = "u"
|
2
|
+
# vim:encoding=UTF-8:
|
3
|
+
$KCODE = "u" unless defined? ::Encoding # json use this
|
4
4
|
=begin
|
5
5
|
|
6
6
|
# tig.rb
|
@@ -145,8 +145,6 @@ Use IM instead of any APIs (e.g. post)
|
|
145
145
|
|
146
146
|
### api_source=<source>
|
147
147
|
|
148
|
-
### max_params_count=<number:3>
|
149
|
-
|
150
148
|
### check_friends_interval=<seconds:3600>
|
151
149
|
|
152
150
|
### check_updates_interval=<seconds:86400>
|
@@ -167,6 +165,10 @@ Set 0 to disable checking.
|
|
167
165
|
|
168
166
|
### shuffled_tmap
|
169
167
|
|
168
|
+
### ll=<lat>,<long>
|
169
|
+
|
170
|
+
### with_retweets
|
171
|
+
|
170
172
|
## Extended commands through the CTCP ACTION
|
171
173
|
|
172
174
|
### list (ls)
|
@@ -180,7 +182,7 @@ Set 0 to disable checking.
|
|
180
182
|
/me fav! [ID...]
|
181
183
|
/me fav NICK
|
182
184
|
|
183
|
-
### link (ln)
|
185
|
+
### link (ln, url, u)
|
184
186
|
|
185
187
|
/me link ID [ID...]
|
186
188
|
|
@@ -197,7 +199,7 @@ Set 0 to disable checking.
|
|
197
199
|
/me reply ID blah, blah...
|
198
200
|
|
199
201
|
### retweet (rt)
|
200
|
-
|
202
|
+
|
201
203
|
/me retweet ID (blah, blah...)
|
202
204
|
|
203
205
|
### utf7 (utf-7)
|
@@ -232,9 +234,10 @@ Ruby's by cho45
|
|
232
234
|
|
233
235
|
=end
|
234
236
|
|
235
|
-
|
237
|
+
case
|
238
|
+
when File.directory?("lib")
|
236
239
|
$LOAD_PATH << "lib"
|
237
|
-
|
240
|
+
when File.directory?(File.expand_path("lib", ".."))
|
238
241
|
$LOAD_PATH << File.expand_path("lib", "..")
|
239
242
|
end
|
240
243
|
|
@@ -258,14 +261,25 @@ end
|
|
258
261
|
module Net::IRC::Constants; RPL_WHOISBOT = "335"; RPL_CREATEONTIME = "329"; end
|
259
262
|
|
260
263
|
class TwitterIrcGateway < Net::IRC::Server::Session
|
264
|
+
@@ctcp_action_commands = []
|
265
|
+
|
266
|
+
class << self
|
267
|
+
def ctcp_action(*commands, &block)
|
268
|
+
name = "+ctcp_action_#{commands.inspect}"
|
269
|
+
define_method(name, block)
|
270
|
+
commands.each do |command|
|
271
|
+
@@ctcp_action_commands << [command, name]
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
261
276
|
def server_name
|
262
277
|
"twittergw"
|
263
278
|
end
|
264
279
|
|
265
280
|
def server_version
|
266
|
-
|
267
|
-
|
268
|
-
"0.0.0#{rev}"
|
281
|
+
head = `git rev-parse HEAD 2>/dev/null`
|
282
|
+
head.empty?? "unknown" : head
|
269
283
|
end
|
270
284
|
|
271
285
|
def available_user_modes
|
@@ -298,13 +312,16 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
298
312
|
|
299
313
|
class APIFailed < StandardError; end
|
300
314
|
|
315
|
+
MAX_MODE_PARAMS = 3
|
316
|
+
WSP_REGEX = Regexp.new("\\r\\n|[\\r\\n\\t#{"\\u00A0\\u1680\\u180E\\u2002-\\u200D\\u202F\\u205F\\u2060\\uFEFF" if "\u0000" == "\000"}]")
|
317
|
+
|
301
318
|
def initialize(*args)
|
302
319
|
super
|
303
320
|
@groups = {}
|
304
321
|
@channels = [] # joined channels (groups)
|
305
322
|
@nicknames = {}
|
306
323
|
@drones = []
|
307
|
-
@config = Pathname.new(ENV["HOME"]) + ".tig"
|
324
|
+
@config = Pathname.new(ENV["HOME"]) + ".tig" ### TODO マルチユーザに対応してない
|
308
325
|
@etags = {}
|
309
326
|
@consums = []
|
310
327
|
@limit = hourly_limit
|
@@ -362,6 +379,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
362
379
|
post server_name, MODE, @nick, "+o"
|
363
380
|
post @prefix, JOIN, main_channel
|
364
381
|
post server_name, MODE, main_channel, "+mto", @nick
|
382
|
+
post server_name, MODE, main_channel, "+q", @nick
|
365
383
|
if @me.status
|
366
384
|
@me.status.user = @me
|
367
385
|
post @prefix, TOPIC, main_channel, generate_status_message(@me.status.text)
|
@@ -425,14 +443,20 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
425
443
|
|
426
444
|
@timeline = TypableMap.new(@opts.tmap_size || 10_404,
|
427
445
|
@opts.shuffled_tmap || false)
|
428
|
-
|
446
|
+
|
447
|
+
if @opts.clientspoofing
|
448
|
+
update_sources
|
449
|
+
else
|
450
|
+
@sources = [api_source]
|
451
|
+
end
|
429
452
|
|
430
453
|
update_redundant_suffix
|
431
454
|
@check_updates_thread = Thread.start do
|
432
|
-
sleep
|
455
|
+
sleep 30
|
433
456
|
|
434
457
|
loop do
|
435
458
|
begin
|
459
|
+
@log.info "check_updates"
|
436
460
|
check_updates
|
437
461
|
rescue Exception => e
|
438
462
|
@log.error e.inspect
|
@@ -443,7 +467,9 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
443
467
|
sleep 0.01 * (90 + rand(21)) *
|
444
468
|
(@opts.check_updates_interval || 86400) # 0.9 ... 1.1 day
|
445
469
|
end
|
446
|
-
|
470
|
+
|
471
|
+
sleep @opts.check_updates_interval || 86400
|
472
|
+
end
|
447
473
|
|
448
474
|
@check_timeline_thread = Thread.start do
|
449
475
|
sleep 2 * (@me.friends_count / 100.0).ceil
|
@@ -511,7 +537,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
511
537
|
def on_privmsg(m)
|
512
538
|
target, mesg = *m.params
|
513
539
|
|
514
|
-
m.ctcps.each {|ctcp| on_ctcp
|
540
|
+
m.ctcps.each {|ctcp| on_ctcp(target, ctcp) } if m.ctcp?
|
515
541
|
|
516
542
|
return if mesg.empty?
|
517
543
|
return on_ctcp_action(target, mesg) if mesg.sub!(/\A +/, "") #and @opts.direct_action
|
@@ -563,29 +589,32 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
563
589
|
return
|
564
590
|
end
|
565
591
|
|
566
|
-
|
592
|
+
q = { :status => mesg, :source => source }
|
593
|
+
|
567
594
|
if @opts.old_style_reply and mesg[/\A@(?>([A-Za-z0-9_]{1,15}))[^A-Za-z0-9_]/]
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
in_reply_to = user.status.id if user.status
|
595
|
+
if user = friend($1) || api("users/show/#{$1}")
|
596
|
+
unless user.status
|
597
|
+
user = api("users/show/#{user.id}", {},
|
598
|
+
{ :authenticate => user.protected })
|
599
|
+
end
|
600
|
+
if user.status
|
601
|
+
q.update :in_reply_to_status_id => user.status.id
|
602
|
+
end
|
577
603
|
end
|
578
604
|
end
|
605
|
+
if @opts.ll
|
606
|
+
lat, long = @opts.ll.split(",", 2)
|
607
|
+
q.update :lat => lat.to_f
|
608
|
+
q.update :long => long.to_f
|
609
|
+
end
|
579
610
|
|
580
|
-
q = { :status => mesg, :source => source }
|
581
|
-
q.update(:in_reply_to_status_id => in_reply_to) if in_reply_to
|
582
611
|
ret = api("statuses/update", q)
|
583
612
|
log oops(ret) if ret.truncated
|
584
613
|
ret.user.status = ret
|
585
614
|
@me = ret.user
|
586
615
|
log "Status updated"
|
587
616
|
end
|
588
|
-
when target.
|
617
|
+
when target.screen_name? # Direct message
|
589
618
|
ret = api("direct_messages/new", { :screen_name => target, :text => mesg })
|
590
619
|
post server_name, NOTICE, @nick, "Your direct message has been sent to #{target}."
|
591
620
|
else
|
@@ -604,7 +633,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
604
633
|
|
605
634
|
def on_whois(m)
|
606
635
|
nick = m.params[0]
|
607
|
-
unless nick.
|
636
|
+
unless nick.screen_name?
|
608
637
|
post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel"
|
609
638
|
return
|
610
639
|
end
|
@@ -635,16 +664,33 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
635
664
|
end
|
636
665
|
|
637
666
|
def on_who(m)
|
638
|
-
channel
|
667
|
+
channel = m.params[0]
|
668
|
+
whoreply = Proc.new do |ch, user|
|
669
|
+
# "<channel> <user> <host> <server> <nick>
|
670
|
+
# ( "H" / "G" > ["*"] [ ( "@" / "+" ) ]
|
671
|
+
# :<hopcount> <real name>"
|
672
|
+
prefix = prefix(user)
|
673
|
+
server = api_base.host
|
674
|
+
mode = case prefix.nick
|
675
|
+
when @nick then "~"
|
676
|
+
#when @drones.include?(user.id) then "%" # FIXME
|
677
|
+
else "+"
|
678
|
+
end
|
679
|
+
hop = prefix.host.count("/")
|
680
|
+
real = user.name
|
681
|
+
post server_name, RPL_WHOREPLY, @nick,
|
682
|
+
ch, prefix.user, prefix.host, server, prefix.nick, "H*#{mode}", "#{hop} #{real}"
|
683
|
+
end
|
684
|
+
|
639
685
|
case
|
640
686
|
when channel.casecmp(main_channel).zero?
|
641
687
|
users = [@me]
|
642
688
|
users.concat @friends.reverse if @friends
|
643
|
-
users.each {|friend| whoreply channel, friend }
|
689
|
+
users.each {|friend| whoreply.call channel, friend }
|
644
690
|
post server_name, RPL_ENDOFWHO, @nick, channel
|
645
691
|
when (@groups.key?(channel) and @friends)
|
646
692
|
@groups[channel].each do |nick|
|
647
|
-
whoreply channel, friend(nick)
|
693
|
+
whoreply.call channel, friend(nick)
|
648
694
|
end
|
649
695
|
post server_name, RPL_ENDOFWHO, @nick, channel
|
650
696
|
else
|
@@ -662,6 +708,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
662
708
|
@channels.uniq!
|
663
709
|
post @prefix, JOIN, channel
|
664
710
|
post server_name, MODE, channel, "+mtio", @nick
|
711
|
+
post server_name, MODE, channel, "+q", @nick
|
665
712
|
save_config
|
666
713
|
end
|
667
714
|
end
|
@@ -676,7 +723,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
676
723
|
|
677
724
|
def on_invite(m)
|
678
725
|
nick, channel = *m.params
|
679
|
-
if not nick.
|
726
|
+
if not nick.screen_name? or @nick.casecmp(nick).zero?
|
680
727
|
post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel" # or yourself
|
681
728
|
return
|
682
729
|
end
|
@@ -737,6 +784,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
737
784
|
channel = m.params[0]
|
738
785
|
return if not channel.casecmp(main_channel).zero? or @me.status.nil?
|
739
786
|
|
787
|
+
return if not @opts.mesautofix
|
740
788
|
begin
|
741
789
|
require "levenshtein"
|
742
790
|
topic = m.params[1]
|
@@ -754,7 +802,8 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
754
802
|
if distance < 0.5
|
755
803
|
deleted = api("statuses/destroy/#{previous.id}")
|
756
804
|
@timeline.delete_if {|tid, s| s.id == deleted.id }
|
757
|
-
log "
|
805
|
+
log "Similar update in previous. Conclude that it has error."
|
806
|
+
log "And overwrite previous as new status: #{status.text}"
|
758
807
|
else
|
759
808
|
log "Status updated"
|
760
809
|
end
|
@@ -766,12 +815,13 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
766
815
|
channel = m.params[0]
|
767
816
|
|
768
817
|
unless m.params[1]
|
769
|
-
|
818
|
+
case
|
819
|
+
when channel.ch?
|
770
820
|
mode = "+mt"
|
771
821
|
mode += "i" unless channel.casecmp(main_channel).zero?
|
772
822
|
post server_name, RPL_CHANNELMODEIS, @nick, channel, mode
|
773
823
|
#post server_name, RPL_CREATEONTIME, @nick, channel, 0
|
774
|
-
|
824
|
+
when channel.casecmp(@nick).zero?
|
775
825
|
post server_name, RPL_UMODEIS, @nick, @nick, "+o"
|
776
826
|
end
|
777
827
|
end
|
@@ -787,303 +837,390 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
787
837
|
def on_ctcp_action(target, mesg)
|
788
838
|
#return unless main_channel.casecmp(target).zero?
|
789
839
|
command, *args = mesg.split(" ")
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
840
|
+
if command
|
841
|
+
command.downcase!
|
842
|
+
|
843
|
+
@@ctcp_action_commands.each do |define, name|
|
844
|
+
if define === command
|
845
|
+
send(name, target, mesg, Regexp.last_match || command, args)
|
846
|
+
break
|
847
|
+
end
|
848
|
+
end
|
849
|
+
else
|
850
|
+
commands = @@ctcp_action_commands.map {|define, name|
|
851
|
+
define
|
852
|
+
}.select {|define|
|
853
|
+
define.is_a? String
|
854
|
+
}
|
855
|
+
|
856
|
+
log "[tig.rb] CTCP ACTION COMMANDS:"
|
857
|
+
commands.each_slice(5) do |c|
|
858
|
+
log c.join(" ")
|
795
859
|
end
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
860
|
+
end
|
861
|
+
|
862
|
+
rescue APIFailed => e
|
863
|
+
log e.inspect
|
864
|
+
rescue Exception => e
|
865
|
+
log e.inspect
|
866
|
+
e.backtrace.each do |l|
|
867
|
+
@log.error "\t#{l}"
|
868
|
+
end
|
869
|
+
end
|
870
|
+
|
871
|
+
ctcp_action "call" do |target, mesg, command, args|
|
872
|
+
if args.size < 2
|
873
|
+
log "/me call <Twitter_screen_name> as <IRC_nickname>"
|
874
|
+
return
|
875
|
+
end
|
876
|
+
screen_name = args[0]
|
877
|
+
nickname = args[2] || args[1] # allow omitting "as"
|
878
|
+
if nickname == "is" and
|
879
|
+
deleted_nick = @nicknames.delete(screen_name)
|
880
|
+
log %Q{Removed the nickname "#{deleted_nick}" for #{screen_name}}
|
881
|
+
else
|
882
|
+
@nicknames[screen_name] = nickname
|
883
|
+
log "Call #{screen_name} as #{nickname}"
|
884
|
+
end
|
885
|
+
#save_config
|
886
|
+
end
|
887
|
+
|
888
|
+
ctcp_action "debug" do |target, mesg, command, args|
|
889
|
+
code = args.join(" ")
|
890
|
+
begin
|
891
|
+
log instance_eval(code).inspect
|
892
|
+
rescue Exception => e
|
893
|
+
log e.inspect
|
894
|
+
end
|
895
|
+
end
|
896
|
+
|
897
|
+
ctcp_action "utf-7", "utf7" do |target, mesg, command, args|
|
898
|
+
unless defined? ::Iconv
|
899
|
+
log "Can't load iconv."
|
900
|
+
return
|
901
|
+
end
|
902
|
+
@utf7 = !@utf7
|
903
|
+
log "UTF-7 mode: #{@utf7 ? 'on' : 'off'}"
|
904
|
+
end
|
905
|
+
|
906
|
+
ctcp_action "list", "ls" do |target, mesg, command, args|
|
907
|
+
if args.empty?
|
908
|
+
log "/me list <NICK> [<NUM>]"
|
909
|
+
return
|
910
|
+
end
|
911
|
+
nick = args.first
|
912
|
+
if not nick.screen_name? or
|
913
|
+
api("users/username_available", { :username => nick }).valid
|
914
|
+
post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel"
|
915
|
+
return
|
916
|
+
end
|
917
|
+
id = nick
|
918
|
+
authenticate = false
|
919
|
+
if user = friend(nick)
|
920
|
+
id = user.id
|
921
|
+
nick = user.screen_name
|
922
|
+
authenticate = user.protected
|
923
|
+
end
|
924
|
+
unless (1..200).include?(count = args[1].to_i)
|
925
|
+
count = 20
|
926
|
+
end
|
927
|
+
begin
|
928
|
+
res = api("statuses/user_timeline/#{id}",
|
929
|
+
{ :count => count }, { :authenticate => authenticate })
|
930
|
+
rescue APIFailed
|
931
|
+
#log "#{nick} has protected their updates."
|
932
|
+
return
|
933
|
+
end
|
934
|
+
res.reverse_each do |s|
|
935
|
+
message(s, target, nil, nil, NOTICE)
|
936
|
+
end
|
937
|
+
end
|
938
|
+
|
939
|
+
ctcp_action %r/\A(un)?fav(?:ou?rite)?(!)?\z/ do |target, mesg, command, args|
|
940
|
+
# fav, unfav, favorite, unfavorite, favourite, unfavourite
|
941
|
+
method = command[1].nil? ? "create" : "destroy"
|
942
|
+
force = !!command[2]
|
943
|
+
entered = command[0].capitalize
|
944
|
+
statuses = []
|
945
|
+
if args.empty?
|
946
|
+
if method == "create"
|
947
|
+
if status = @timeline.last
|
948
|
+
statuses << status
|
949
|
+
else
|
950
|
+
#log ""
|
951
|
+
return
|
952
|
+
end
|
801
953
|
else
|
802
|
-
@
|
803
|
-
|
954
|
+
@favorites ||= api("favorites").reverse
|
955
|
+
if @favorites.empty?
|
956
|
+
log "You've never favorite yet. No favorites to unfavorite."
|
957
|
+
return
|
958
|
+
end
|
959
|
+
statuses.push @favorites.last
|
804
960
|
end
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
961
|
+
else
|
962
|
+
args.each do |tid_or_nick|
|
963
|
+
case
|
964
|
+
when status = @timeline[tid = tid_or_nick]
|
965
|
+
statuses.push status
|
966
|
+
when friend = friend(nick = tid_or_nick)
|
967
|
+
if friend.status
|
968
|
+
statuses.push friend.status
|
969
|
+
else
|
970
|
+
log "#{tid_or_nick} has no status."
|
971
|
+
end
|
972
|
+
else
|
973
|
+
# PRIVMSG: fav nick
|
974
|
+
log "No such ID/NICK #{@opts.tid % tid_or_nick}"
|
975
|
+
end
|
810
976
|
end
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
if
|
815
|
-
|
816
|
-
|
977
|
+
end
|
978
|
+
@favorites ||= []
|
979
|
+
statuses.each do |s|
|
980
|
+
if not force and method == "create" and
|
981
|
+
@favorites.find {|i| i.id == s.id }
|
982
|
+
log "The status is already favorited! <#{permalink(s)}>"
|
983
|
+
next
|
817
984
|
end
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
985
|
+
res = api("favorites/#{method}/#{s.id}")
|
986
|
+
log "#{entered}: #{res.user.screen_name}: #{generate_status_message(res.text)}"
|
987
|
+
if method == "create"
|
988
|
+
@favorites.push res
|
989
|
+
else
|
990
|
+
@favorites.delete_if {|i| i.id == res.id }
|
823
991
|
end
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
992
|
+
end
|
993
|
+
end
|
994
|
+
|
995
|
+
ctcp_action "link", "ln", /\Au(?:rl)?\z/ do |target, mesg, command, args|
|
996
|
+
args.each do |tid|
|
997
|
+
if status = @timeline[tid]
|
998
|
+
log "#{@opts.tid % tid}: #{permalink(status)}"
|
999
|
+
else
|
1000
|
+
log "No such ID #{@opts.tid % tid}"
|
830
1001
|
end
|
831
|
-
|
832
|
-
|
1002
|
+
end
|
1003
|
+
end
|
1004
|
+
|
1005
|
+
ctcp_action "ratio", "ratios" do |target, mesg, command, args|
|
1006
|
+
unless args.empty?
|
1007
|
+
args = args.first.split(":") if args.size == 1
|
1008
|
+
case
|
1009
|
+
when @opts.dm && @opts.mentions && args.size < 3
|
1010
|
+
log "/me ratios <timeline> <dm> <mentions>"
|
1011
|
+
return
|
1012
|
+
when @opts.dm && args.size < 2
|
1013
|
+
log "/me ratios <timeline> <dm>"
|
1014
|
+
return
|
1015
|
+
when @opts.mentions && args.size < 2
|
1016
|
+
log "/me ratios <timeline> <mentions>"
|
1017
|
+
return
|
833
1018
|
end
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
rescue APIFailed
|
838
|
-
#log "#{nick} has protected their updates."
|
1019
|
+
ratios = args.map {|ratio| ratio.to_f }
|
1020
|
+
if ratios.any? {|ratio| ratio <= 0.0 }
|
1021
|
+
log "Ratios must be greater than 0.0 and fractional values are permitted."
|
839
1022
|
return
|
840
1023
|
end
|
841
|
-
|
842
|
-
|
1024
|
+
@ratio.timeline = ratios[0]
|
1025
|
+
|
1026
|
+
case
|
1027
|
+
when @opts.dm
|
1028
|
+
@ratio.dm = ratios[1]
|
1029
|
+
@ratio.mentions = ratios[2] if @opts.mentions
|
1030
|
+
when @opts.mentions
|
1031
|
+
@ratio.mentions = ratios[1]
|
843
1032
|
end
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
end
|
858
|
-
else
|
859
|
-
@favorites ||= api("favorites").reverse
|
860
|
-
if @favorites.empty?
|
861
|
-
log "You've never favorite yet. No favorites to unfavorite."
|
862
|
-
return
|
863
|
-
end
|
864
|
-
statuses.push @favorites.last
|
865
|
-
end
|
866
|
-
else
|
867
|
-
args.each do |tid_or_nick|
|
868
|
-
case
|
869
|
-
when status = @timeline[tid_or_nick]
|
1033
|
+
end
|
1034
|
+
log "Intervals: " + @ratio.zip([:timeline, :dm, :mentions]).map {|ratio, name| [name, "#{interval(ratio).round}sec"] }.inspect
|
1035
|
+
end
|
1036
|
+
|
1037
|
+
ctcp_action "rm", %r/\A(?:de(?:stroy|l(?:ete)?)|miss|oops|r(?:emove|m))\z/ do |target, mesg, command, args|
|
1038
|
+
# destroy, delete, del, remove, rm, miss, oops
|
1039
|
+
statuses = []
|
1040
|
+
if args.empty? and @me.status
|
1041
|
+
statuses.push @me.status
|
1042
|
+
else
|
1043
|
+
args.each do |tid|
|
1044
|
+
if status = @timeline[tid]
|
1045
|
+
if status.user.id == @me.id
|
870
1046
|
statuses.push status
|
871
|
-
when friend = friend(tid_or_nick)
|
872
|
-
if friend.status
|
873
|
-
statuses.push friend.status
|
874
|
-
else
|
875
|
-
log "#{tid_or_nick} has no status."
|
876
|
-
end
|
877
1047
|
else
|
878
|
-
#
|
879
|
-
log "No such ID/NICK #{@opts.tid % tid_or_nick}"
|
1048
|
+
log "The status you specified by the ID #{@opts.tid % tid} is not yours."
|
880
1049
|
end
|
881
|
-
end
|
882
|
-
end
|
883
|
-
@favorites ||= []
|
884
|
-
statuses.each do |s|
|
885
|
-
if not force and method == "create" and
|
886
|
-
@favorites.find {|i| i.id == s.id }
|
887
|
-
log "The status is already favorited! <#{permalink(s)}>"
|
888
|
-
next
|
889
|
-
end
|
890
|
-
res = api("favorites/#{method}/#{s.id}")
|
891
|
-
log "#{entered}: #{res.user.screen_name}: #{generate_status_message(res.text)}"
|
892
|
-
if method == "create"
|
893
|
-
@favorites.push res
|
894
|
-
else
|
895
|
-
@favorites.delete_if {|i| i.id == res.id }
|
896
|
-
end
|
897
|
-
end
|
898
|
-
when "link", "ln"
|
899
|
-
args.each do |tid|
|
900
|
-
if status = @timeline[tid]
|
901
|
-
log "#{@opts.tid % tid}: #{permalink(status)}"
|
902
1050
|
else
|
903
1051
|
log "No such ID #{@opts.tid % tid}"
|
904
1052
|
end
|
905
1053
|
end
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
end
|
924
|
-
@ratio.timeline = ratios[0]
|
925
|
-
if @opts.dm
|
926
|
-
@ratio.dm = ratios[1]
|
927
|
-
@ratio.mentions = ratios[2] if @opts.mentions
|
928
|
-
elsif @opts.mentions
|
929
|
-
@ratio.mentions = ratios[1]
|
930
|
-
end
|
931
|
-
end
|
932
|
-
log "Intervals: #{@ratio.map {|ratio| interval ratio }.inspect}"
|
933
|
-
when /\A(?:de(?:stroy|l(?:ete)?)|miss|oops|r(?:emove|m))\z/
|
934
|
-
# destroy, delete, del, remove, rm, miss, oops
|
935
|
-
statuses = []
|
936
|
-
if args.empty? and @me.status
|
937
|
-
statuses.push @me.status
|
938
|
-
else
|
939
|
-
args.each do |tid|
|
940
|
-
if status = @timeline[tid]
|
941
|
-
if status.user.id == @me.id
|
942
|
-
statuses.push status
|
943
|
-
else
|
944
|
-
log "The status you specified by the ID #{@opts.tid % tid} is not yours."
|
945
|
-
end
|
946
|
-
else
|
947
|
-
log "No such ID #{@opts.tid % tid}"
|
1054
|
+
end
|
1055
|
+
b = false
|
1056
|
+
statuses.each do |st|
|
1057
|
+
res = api("statuses/destroy/#{st.id}")
|
1058
|
+
@timeline.delete_if {|tid, s| s.id == res.id }
|
1059
|
+
b = @me.status && @me.status.id == res.id
|
1060
|
+
log "Destroyed: #{res.text}"
|
1061
|
+
end
|
1062
|
+
Thread.start do
|
1063
|
+
sleep 2
|
1064
|
+
@me = api("account/update_profile") #api("account/verify_credentials")
|
1065
|
+
if @me.status
|
1066
|
+
@me.status.user = @me
|
1067
|
+
msg = generate_status_message(@me.status.text)
|
1068
|
+
@timeline.any? do |tid, s|
|
1069
|
+
if s.id == @me.status.id
|
1070
|
+
msg << " " << @opts.tid % tid
|
948
1071
|
end
|
949
1072
|
end
|
1073
|
+
post @prefix, TOPIC, main_channel, msg
|
950
1074
|
end
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
Thread.start do
|
959
|
-
sleep 2
|
960
|
-
@me = api("account/update_profile") #api("account/verify_credentials")
|
961
|
-
if @me.status
|
962
|
-
@me.status.user = @me
|
963
|
-
msg = generate_status_message(@me.status.text)
|
964
|
-
@timeline.any? do |tid, s|
|
965
|
-
if s.id == @me.status.id
|
966
|
-
msg << " " << @opts.tid % tid
|
967
|
-
end
|
968
|
-
end
|
969
|
-
post @prefix, TOPIC, main_channel, msg
|
970
|
-
end
|
971
|
-
end if b
|
972
|
-
when "name"
|
973
|
-
name = mesg.split(" ", 2)[1]
|
974
|
-
unless name.nil?
|
975
|
-
@me = api("account/update_profile", { :name => name })
|
976
|
-
@me.status.user = @me if @me.status
|
977
|
-
log "You are named #{@me.name}."
|
978
|
-
end
|
979
|
-
when "email"
|
980
|
-
# FIXME
|
981
|
-
email = args.first
|
982
|
-
unless email.nil?
|
983
|
-
@me = api("account/update_profile", { :email => email })
|
984
|
-
@me.status.user = @me if @me.status
|
985
|
-
end
|
986
|
-
when "url"
|
987
|
-
# FIXME
|
988
|
-
url = args.first || ""
|
989
|
-
@me = api("account/update_profile", { :url => url })
|
990
|
-
@me.status.user = @me if @me.status
|
991
|
-
when "in", "location"
|
992
|
-
location = mesg.split(" ", 2)[1] || ""
|
993
|
-
@me = api("account/update_profile", { :location => location })
|
1075
|
+
end if b
|
1076
|
+
end
|
1077
|
+
|
1078
|
+
ctcp_action "name" do |target, mesg, command, args|
|
1079
|
+
name = mesg.split(" ", 2)[1]
|
1080
|
+
unless name.nil?
|
1081
|
+
@me = api("account/update_profile", { :name => name })
|
994
1082
|
@me.status.user = @me if @me.status
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1083
|
+
log "You are named #{@me.name}."
|
1084
|
+
end
|
1085
|
+
end
|
1086
|
+
|
1087
|
+
ctcp_action "email" do |target, mesg, command, args|
|
1088
|
+
# FIXME
|
1089
|
+
email = args.first
|
1090
|
+
unless email.nil?
|
1091
|
+
@me = api("account/update_profile", { :email => email })
|
1001
1092
|
@me.status.user = @me if @me.status
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1005
|
-
|
1006
|
-
#
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1093
|
+
end
|
1094
|
+
end
|
1095
|
+
|
1096
|
+
ctcp_action "url" do |target, mesg, command, args|
|
1097
|
+
# FIXME
|
1098
|
+
url = args.first || ""
|
1099
|
+
@me = api("account/update_profile", { :url => url })
|
1100
|
+
@me.status.user = @me if @me.status
|
1101
|
+
end
|
1102
|
+
|
1103
|
+
ctcp_action "in", "location" do |target, mesg, command, args|
|
1104
|
+
location = mesg.split(" ", 2)[1] || ""
|
1105
|
+
@me = api("account/update_profile", { :location => location })
|
1106
|
+
@me.status.user = @me if @me.status
|
1107
|
+
location = (@me.location and @me.location.empty?) ? "nowhere" : "in #{@me.location}"
|
1108
|
+
log "You are #{location} now."
|
1109
|
+
end
|
1110
|
+
|
1111
|
+
ctcp_action %r/\Adesc(?:ription)?\z/ do |target, mesg, command, args|
|
1112
|
+
# FIXME
|
1113
|
+
description = mesg.split(" ", 2)[1] || ""
|
1114
|
+
@me = api("account/update_profile", { :description => description })
|
1115
|
+
@me.status.user = @me if @me.status
|
1116
|
+
end
|
1117
|
+
|
1118
|
+
ctcp_action %r/\A(?:mention|re(?:ply)?)\z/ do |target, mesg, command, args|
|
1119
|
+
# reply, re, mention
|
1120
|
+
tid = args.first
|
1121
|
+
if status = @timeline[tid]
|
1122
|
+
text = mesg.split(" ", 3)[2]
|
1123
|
+
screen_name = "@#{status.user.screen_name}"
|
1124
|
+
if text.nil? or not text.include?(screen_name)
|
1125
|
+
text = "#{screen_name} #{text}"
|
1126
|
+
end
|
1127
|
+
ret = api("statuses/update", { :status => text, :source => source,
|
1128
|
+
:in_reply_to_status_id => status.id })
|
1129
|
+
log oops(ret) if ret.truncated
|
1130
|
+
msg = generate_status_message(status.text)
|
1131
|
+
url = permalink(status)
|
1132
|
+
log "Status updated (In reply to #{@opts.tid % tid}: #{msg} <#{url}>)"
|
1133
|
+
ret.user.status = ret
|
1134
|
+
@me = ret.user
|
1135
|
+
end
|
1136
|
+
end
|
1137
|
+
|
1138
|
+
ctcp_action %r/\Aspoo(o+)?f\z/ do |target, mesg, command, args|
|
1139
|
+
if args.empty?
|
1027
1140
|
Thread.start do
|
1028
|
-
|
1029
|
-
? @sources.size == 1 || $1 ? fetch_sources($1 && $1.size) \
|
1030
|
-
: [[api_source, "tig.rb"]] \
|
1031
|
-
: args.map {|src| [src.upcase != "WEB" ? src : "", "=#{src}"] }
|
1032
|
-
log @sources.map {|src| src[1] }.sort.join(", ")
|
1033
|
-
end
|
1034
|
-
when "bot", "drone"
|
1035
|
-
if args.empty?
|
1036
|
-
log "/me bot <NICK> [<NICK>...]"
|
1037
|
-
return
|
1141
|
+
update_sources(command[1].nil?? 0 : command[1].size)
|
1038
1142
|
end
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
else
|
1049
|
-
@drones << user.id
|
1050
|
-
mode = "+#{mode}"
|
1051
|
-
log "Marks #{bot} as a bot."
|
1052
|
-
end
|
1143
|
+
return
|
1144
|
+
end
|
1145
|
+
names = []
|
1146
|
+
@sources = args.map do |arg|
|
1147
|
+
names << "=#{arg}"
|
1148
|
+
case arg.upcase
|
1149
|
+
when "WEB" then ""
|
1150
|
+
when "API" then nil
|
1151
|
+
else arg
|
1053
1152
|
end
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1153
|
+
end
|
1154
|
+
log(names.inject([]) do |r, name|
|
1155
|
+
s = r.join(", ")
|
1156
|
+
if s.size < 400
|
1157
|
+
r << name
|
1158
|
+
else
|
1159
|
+
log s
|
1160
|
+
[name]
|
1059
1161
|
end
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1162
|
+
end.join(", "))
|
1163
|
+
end
|
1164
|
+
|
1165
|
+
ctcp_action "bot", "drone" do |target, mesg, command, args|
|
1166
|
+
if args.empty?
|
1167
|
+
log "/me bot <NICK> [<NICK>...]"
|
1168
|
+
return
|
1169
|
+
end
|
1170
|
+
args.each do |bot|
|
1171
|
+
user = friend(bot)
|
1172
|
+
unless user
|
1173
|
+
post server_name, ERR_NOSUCHNICK, bot, "No such nick/channel"
|
1174
|
+
next
|
1065
1175
|
end
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
comment = ""
|
1074
|
-
end
|
1075
|
-
screen_name = "@#{status.user.screen_name}"
|
1076
|
-
rt_message = generate_status_message(status.text)
|
1077
|
-
text = "#{comment}RT #{screen_name}: #{rt_message}"
|
1078
|
-
ret = api("statuses/update", { :status => text, :source => source })
|
1079
|
-
log oops(ret) if ret.truncated
|
1080
|
-
log "Status updated (RT to #{@opts.tid % tid}: #{text})"
|
1081
|
-
ret.user.status = ret
|
1082
|
-
@me = ret.user
|
1176
|
+
if @drones.delete(user.id)
|
1177
|
+
mode = "-#{mode}"
|
1178
|
+
log "#{bot} is no longer a bot."
|
1179
|
+
else
|
1180
|
+
@drones << user.id
|
1181
|
+
mode = "+#{mode}"
|
1182
|
+
log "Marks #{bot} as a bot."
|
1083
1183
|
end
|
1084
|
-
end
|
1085
|
-
|
1086
|
-
|
1184
|
+
end
|
1185
|
+
save_config
|
1186
|
+
|
1187
|
+
end
|
1188
|
+
|
1189
|
+
ctcp_action "home", "h" do |target, mesg, command, args|
|
1190
|
+
if args.empty?
|
1191
|
+
log "/me home <NICK>"
|
1192
|
+
return
|
1193
|
+
end
|
1194
|
+
nick = args.first
|
1195
|
+
if not nick.screen_name? or
|
1196
|
+
api("users/username_available", { :username => nick }).valid
|
1197
|
+
post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel"
|
1198
|
+
return
|
1199
|
+
end
|
1200
|
+
log "http://twitter.com/#{nick}"
|
1201
|
+
end
|
1202
|
+
|
1203
|
+
ctcp_action "retweet", "rt" do |target, mesg, command, args|
|
1204
|
+
if args.empty?
|
1205
|
+
log "/me #{command} <ID> blah blah"
|
1206
|
+
return
|
1207
|
+
end
|
1208
|
+
tid = args.first
|
1209
|
+
if status = @timeline[tid]
|
1210
|
+
if args.size >= 2
|
1211
|
+
comment = mesg.split(" ", 3)[2] + " "
|
1212
|
+
else
|
1213
|
+
comment = ""
|
1214
|
+
end
|
1215
|
+
screen_name = "@#{status.user.screen_name}"
|
1216
|
+
rt_message = generate_status_message(status.text)
|
1217
|
+
text = "#{comment}RT #{screen_name}: #{rt_message}"
|
1218
|
+
ret = api("statuses/update", { :status => text, :source => source })
|
1219
|
+
log oops(ret) if ret.truncated
|
1220
|
+
log "Status updated (RT to #{@opts.tid % tid}: #{text})"
|
1221
|
+
ret.user.status = ret
|
1222
|
+
@me = ret.user
|
1223
|
+
end
|
1087
1224
|
end
|
1088
1225
|
|
1089
1226
|
def on_ctcp_clientinfo(target, msg)
|
@@ -1129,7 +1266,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
1129
1266
|
rest = @friends.map do |i|
|
1130
1267
|
prefix = "+" #@drones.include?(i.id) ? "%" : "+" # FIXME ~&%
|
1131
1268
|
"#{prefix}#{i.screen_name}"
|
1132
|
-
end.reverse.inject("
|
1269
|
+
end.reverse.inject("~#{@nick}") do |r, nick|
|
1133
1270
|
if r.size < 400
|
1134
1271
|
r << " " << nick
|
1135
1272
|
else
|
@@ -1166,15 +1303,20 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
1166
1303
|
end
|
1167
1304
|
|
1168
1305
|
def check_timeline
|
1169
|
-
cmd
|
1170
|
-
|
1171
|
-
|
1306
|
+
cmd = PRIVMSG
|
1307
|
+
path = "statuses/#{@opts.with_retweets ? "home" : "friends"}_timeline"
|
1308
|
+
q = { :count => 200 }
|
1309
|
+
@latest_id ||= nil
|
1310
|
+
|
1311
|
+
case
|
1312
|
+
when @latest_id
|
1172
1313
|
q.update(:since_id => @latest_id)
|
1173
|
-
|
1174
|
-
|
1314
|
+
when is_first_retrieve = !@me.statuses_count.zero? && !@me.friends_count.zero?
|
1315
|
+
# cmd = NOTICE # デバッグするときめんどくさいので
|
1316
|
+
q.update(:count => 20)
|
1175
1317
|
end
|
1176
1318
|
|
1177
|
-
api(
|
1319
|
+
api(path, q).reverse_each do |status|
|
1178
1320
|
id = @latest_id = status.id
|
1179
1321
|
next if @timeline.any? {|tid, s| s.id == id }
|
1180
1322
|
|
@@ -1196,6 +1338,9 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
1196
1338
|
b = false
|
1197
1339
|
@friends.each_with_index do |friend, i|
|
1198
1340
|
if b = friend.id == user.id
|
1341
|
+
if friend.screen_name != user.screen_name
|
1342
|
+
post prefix(friend), NICK, user.screen_name
|
1343
|
+
end
|
1199
1344
|
@friends[i] = user
|
1200
1345
|
break
|
1201
1346
|
end
|
@@ -1265,43 +1410,41 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
1265
1410
|
def check_updates
|
1266
1411
|
update_redundant_suffix
|
1267
1412
|
|
1268
|
-
|
1269
|
-
rev = $1.to_i
|
1270
|
-
uri = URI("http://svn.coderepos.org/share/lang/ruby/net-irc/trunk/examples/tig.rb")
|
1413
|
+
uri = URI("http://github.com/api/v1/json/cho45/net-irc/commits/master")
|
1271
1414
|
@log.debug uri.inspect
|
1272
|
-
res = http(uri).request(http_req(:
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1278
|
-
log "\002New version is available.\017 <#{uri}>"
|
1415
|
+
res = http(uri).request(http_req(:get, uri))
|
1416
|
+
|
1417
|
+
latest = JSON.parse(res.body)['commits'][0]['id']
|
1418
|
+
unless server_version == latest
|
1419
|
+
log "\002New version is available.\017 run 'git pull'."
|
1420
|
+
end
|
1279
1421
|
rescue Errno::ECONNREFUSED, Timeout::Error => e
|
1280
1422
|
@log.error "Failed to get the latest revision of tig.rb from #{uri.host}: #{e.inspect}"
|
1281
1423
|
end
|
1282
1424
|
|
1283
1425
|
def interval(ratio)
|
1284
1426
|
now = Time.now
|
1285
|
-
max = @opts.maxlimit
|
1427
|
+
max = @opts.maxlimit || 0
|
1286
1428
|
limit = 0.98 * @limit # 98% of the rate limit
|
1287
1429
|
i = 3600.0 # an hour in seconds
|
1288
1430
|
i *= @ratio.inject {|sum, r| sum.to_f + r.to_f } +
|
1289
1431
|
@consums.delete_if {|t| t < now }.size
|
1290
1432
|
i /= ratio.to_f
|
1291
|
-
i /= (
|
1433
|
+
i /= (0 < max && max < limit) ? max : limit
|
1434
|
+
i = 60 * 30 if i > 60 * 30 # 30分以上止まらないように。
|
1435
|
+
i
|
1292
1436
|
rescue => e
|
1293
1437
|
@log.error e.inspect
|
1294
1438
|
100
|
1295
1439
|
end
|
1296
1440
|
|
1297
1441
|
def join(channel, users)
|
1298
|
-
max_params_count = @opts.max_params_count || 3
|
1299
1442
|
params = []
|
1300
1443
|
users.each do |user|
|
1301
1444
|
prefix = prefix(user)
|
1302
1445
|
post prefix, JOIN, channel
|
1303
|
-
params << prefix.nick
|
1304
|
-
next if params.size <
|
1446
|
+
params << prefix.nick if user.protected
|
1447
|
+
next if params.size < MAX_MODE_PARAMS
|
1305
1448
|
|
1306
1449
|
post server_name, MODE, channel, "+#{"v" * params.size}", *params
|
1307
1450
|
params = []
|
@@ -1347,22 +1490,6 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
1347
1490
|
end
|
1348
1491
|
end
|
1349
1492
|
|
1350
|
-
def whoreply(channel, user)
|
1351
|
-
# "<channel> <user> <host> <server> <nick>
|
1352
|
-
# ( "H" / "G" > ["*"] [ ( "@" / "+" ) ]
|
1353
|
-
# :<hopcount> <real name>"
|
1354
|
-
prefix = prefix(user)
|
1355
|
-
server = api_base.host
|
1356
|
-
real = user.name
|
1357
|
-
mode = case prefix.nick
|
1358
|
-
when @nick then "@"
|
1359
|
-
#when @drones.include?(user.id) then "%" # FIXME
|
1360
|
-
else "+"
|
1361
|
-
end
|
1362
|
-
post server_name, RPL_WHOREPLY, @nick, channel,
|
1363
|
-
prefix.user, prefix.host, server, prefix.nick, "H*#{mode}", "0 #{real}"
|
1364
|
-
end
|
1365
|
-
|
1366
1493
|
def save_config
|
1367
1494
|
config = {
|
1368
1495
|
:groups => @groups,
|
@@ -1397,6 +1524,10 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
1397
1524
|
}x === path
|
1398
1525
|
end
|
1399
1526
|
|
1527
|
+
#def require_put?(path)
|
1528
|
+
# %r{ \A status(?:es)?/retweet (?:/|\z) }x === path
|
1529
|
+
#end
|
1530
|
+
|
1400
1531
|
def api(path, query = {}, opts = {})
|
1401
1532
|
path.sub!(%r{\A/+}, "")
|
1402
1533
|
query = query.to_query_str
|
@@ -1407,7 +1538,6 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
1407
1538
|
uri.path += path
|
1408
1539
|
uri.path += ".json" if path != "users/username_available"
|
1409
1540
|
uri.query = query unless query.empty?
|
1410
|
-
@log.debug uri.inspect
|
1411
1541
|
|
1412
1542
|
header = {}
|
1413
1543
|
credentials = authenticate ? [@real, @pass] : nil
|
@@ -1416,15 +1546,19 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
1416
1546
|
http_req :delete, uri, header, credentials
|
1417
1547
|
when require_post?(path)
|
1418
1548
|
http_req :post, uri, header, credentials
|
1549
|
+
#when require_put?(path)
|
1550
|
+
# http_req :put, uri, header, credentials
|
1419
1551
|
else
|
1420
1552
|
http_req :get, uri, header, credentials
|
1421
1553
|
end
|
1422
1554
|
|
1555
|
+
@log.debug [req.method, uri.to_s]
|
1423
1556
|
ret = http(uri, 30, 30).request req
|
1424
1557
|
|
1425
1558
|
#@etags[uri.to_s] = ret["ETag"]
|
1426
1559
|
|
1427
|
-
|
1560
|
+
case
|
1561
|
+
when authenticate
|
1428
1562
|
hourly_limit = ret["X-RateLimit-Limit"].to_i
|
1429
1563
|
unless hourly_limit.zero?
|
1430
1564
|
if @limit != hourly_limit
|
@@ -1445,7 +1579,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
1445
1579
|
@consums << expired_on
|
1446
1580
|
end
|
1447
1581
|
end
|
1448
|
-
|
1582
|
+
when ret["X-RateLimit-Remaining"]
|
1449
1583
|
@limit_remaining_for_ip = ret["X-RateLimit-Remaining"].to_i
|
1450
1584
|
@log.debug "IP based limit: #{@limit_remaining_for_ip}"
|
1451
1585
|
end
|
@@ -1470,8 +1604,10 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
1470
1604
|
when Net::HTTPBadRequest # 400: exceeded the rate limitation
|
1471
1605
|
if ret.key?("X-RateLimit-Reset")
|
1472
1606
|
s = ret["X-RateLimit-Reset"].to_i - Time.now.to_i
|
1473
|
-
|
1474
|
-
|
1607
|
+
if s > 0
|
1608
|
+
log "RateLimit: #{(s / 60.0).ceil} min remaining to get timeline"
|
1609
|
+
sleep (s > 60 * 10) ? 60 * 10 : s # 10 分に一回はとってくるように
|
1610
|
+
end
|
1475
1611
|
end
|
1476
1612
|
raise APIFailed, "#{ret.code}: #{ret.message}"
|
1477
1613
|
when Net::HTTPUnauthorized # 401
|
@@ -1503,8 +1639,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
1503
1639
|
mesg.delete!("\000\001")
|
1504
1640
|
mesg.gsub!(">", ">")
|
1505
1641
|
mesg.gsub!("<", "<")
|
1506
|
-
|
1507
|
-
mesg.gsub!(/\r\n|[\r\n\t]/, " ")
|
1642
|
+
mesg.gsub!(WSP_REGEX, " ")
|
1508
1643
|
mesg = untinyurl(mesg)
|
1509
1644
|
mesg.sub!(@rsuffix_regex, "") if @rsuffix_regex
|
1510
1645
|
mesg.strip
|
@@ -1563,7 +1698,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
1563
1698
|
end
|
1564
1699
|
|
1565
1700
|
def decode_utf7(str)
|
1566
|
-
return str unless defined?
|
1701
|
+
return str unless defined? ::Iconv and str.include?("+")
|
1567
1702
|
|
1568
1703
|
str.sub!(/\A(?:.+ > |.+\z)/) { Iconv.iconv("UTF-8", "UTF-7", $&).join }
|
1569
1704
|
#FIXME str = "[utf7]: #{str}" if str =~ /[^a-z0-9\s]/i
|
@@ -1593,8 +1728,10 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
1593
1728
|
login, key, len = @opts.bitlify.split(":", 3) if @opts.bitlify
|
1594
1729
|
len = (len || 20).to_i
|
1595
1730
|
longurls = URI.extract(text, %w[http https]).uniq.map do |url|
|
1596
|
-
URI.
|
1597
|
-
end.reject
|
1731
|
+
URI.rstrip url
|
1732
|
+
end.reject do |url|
|
1733
|
+
url.size < len
|
1734
|
+
end
|
1598
1735
|
return text if longurls.empty?
|
1599
1736
|
|
1600
1737
|
bitly = URI("http://api.bit.ly/shorten")
|
@@ -1637,7 +1774,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
1637
1774
|
size = unu_url.size
|
1638
1775
|
|
1639
1776
|
text.gsub(URI.regexp(%w[http https])) do |url|
|
1640
|
-
url = URI.
|
1777
|
+
url = URI.rstrip url
|
1641
1778
|
if url.size < size + 5 or url[0, size] == unu_url
|
1642
1779
|
return url
|
1643
1780
|
end
|
@@ -1684,12 +1821,12 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
1684
1821
|
end
|
1685
1822
|
|
1686
1823
|
urls = []
|
1687
|
-
|
1824
|
+
text.split(/[\s<>]+/).each do |str|
|
1688
1825
|
next if /%[0-9A-Fa-f]{2}/ === str
|
1689
1826
|
# URI::UNSAFE + "#"
|
1690
1827
|
escaped_str = URI.escape(str, %r{[^-_.!~*'()a-zA-Z0-9;/?:@&=+$,\[\]#]})
|
1691
1828
|
URI.extract(escaped_str, %w[http https]).each do |url|
|
1692
|
-
uri = URI(URI.
|
1829
|
+
uri = URI(URI.rstrip(url))
|
1693
1830
|
if not urls.include?(uri.to_s) and exist_uri?(uri)
|
1694
1831
|
urls << uri.to_s
|
1695
1832
|
end
|
@@ -1764,21 +1901,40 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
1764
1901
|
uri
|
1765
1902
|
end
|
1766
1903
|
|
1767
|
-
def
|
1768
|
-
|
1769
|
-
|
1904
|
+
def update_sources(n = 0)
|
1905
|
+
if @sources and @sources.size > 1 and n.zero?
|
1906
|
+
log "tig.rb"
|
1907
|
+
@sources = [api_source]
|
1908
|
+
return @sources
|
1909
|
+
end
|
1910
|
+
|
1911
|
+
uri = URI("http://wedata.net/databases/TwitterSources/items.json")
|
1770
1912
|
@log.debug uri.inspect
|
1771
|
-
json
|
1913
|
+
json = http(uri).request(http_req(:get, uri)).body
|
1772
1914
|
sources = JSON.parse json
|
1773
|
-
sources.map! {|item| [item["data"]["source"], item["name"]] }
|
1774
|
-
|
1775
|
-
|
1776
|
-
|
1777
|
-
sources
|
1915
|
+
sources.map! {|item| [item["data"]["source"], item["name"]] }
|
1916
|
+
sources.push ["", "web"]
|
1917
|
+
sources.push [nil, "API"]
|
1918
|
+
|
1919
|
+
sources = Array.new(n) do
|
1920
|
+
sources.delete_at(rand(sources.size))
|
1921
|
+
end if (1 ... sources.size).include?(n)
|
1922
|
+
|
1923
|
+
log(sources.inject([]) do |r, src|
|
1924
|
+
s = r.join(", ")
|
1925
|
+
if s.size < 400
|
1926
|
+
r << src[1]
|
1927
|
+
else
|
1928
|
+
log s
|
1929
|
+
[src[1]]
|
1930
|
+
end
|
1931
|
+
end.join(", ")) if @sources
|
1932
|
+
|
1933
|
+
@sources = sources.map {|src| src[0] }
|
1778
1934
|
rescue => e
|
1779
1935
|
@log.error e.inspect
|
1780
1936
|
log "An error occured while loading #{uri.host}."
|
1781
|
-
@sources
|
1937
|
+
@sources ||= [api_source]
|
1782
1938
|
end
|
1783
1939
|
|
1784
1940
|
def update_redundant_suffix
|
@@ -1867,33 +2023,34 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
1867
2023
|
end
|
1868
2024
|
|
1869
2025
|
def source
|
1870
|
-
@sources[rand(@sources.size)]
|
2026
|
+
@sources[rand(@sources.size)]
|
1871
2027
|
end
|
1872
2028
|
|
1873
2029
|
def initial_message
|
1874
2030
|
super
|
1875
2031
|
post server_name, RPL_ISUPPORT, @nick,
|
2032
|
+
"PREFIX=(qov)~@%+", "CHANTYPES=#", "CHANMODES=#{available_channel_modes}",
|
2033
|
+
"MODES=#{MAX_MODE_PARAMS}", "NICKLEN=15", "TOPICLEN=420", "CHANNELLEN=50",
|
1876
2034
|
"NETWORK=Twitter",
|
1877
|
-
"CHANTYPES=#", "CHANNELLEN=50", "CHANMODES=#{available_channel_modes}",
|
1878
|
-
"NICKLEN=15", "TOPICLEN=420", "PREFIX=(hov)%@+",
|
1879
2035
|
"are supported by this server"
|
1880
2036
|
end
|
1881
2037
|
|
1882
2038
|
User = Struct.new(:id, :name, :screen_name, :location, :description, :url,
|
1883
2039
|
:following, :notifications, :protected, :time_zone,
|
1884
2040
|
:utc_offset, :created_at, :friends_count, :followers_count,
|
1885
|
-
:statuses_count, :favourites_count, :verified,
|
1886
|
-
:verified_profile,
|
2041
|
+
:statuses_count, :favourites_count, :verified, :geo_enabled,
|
1887
2042
|
:profile_image_url, :profile_background_color, :profile_text_color,
|
1888
2043
|
:profile_link_color, :profile_sidebar_fill_color,
|
1889
2044
|
:profile_sidebar_border_color, :profile_background_image_url,
|
1890
2045
|
:profile_background_tile, :status)
|
1891
|
-
Status = Struct.new(:id, :text, :source, :created_at, :truncated, :favorited,
|
2046
|
+
Status = Struct.new(:id, :text, :source, :created_at, :truncated, :favorited, :geo,
|
1892
2047
|
:in_reply_to_status_id, :in_reply_to_user_id,
|
1893
2048
|
:in_reply_to_screen_name, :user)
|
1894
2049
|
DM = Struct.new(:id, :text, :created_at,
|
1895
2050
|
:sender_id, :sender_screen_name, :sender,
|
1896
2051
|
:recipient_id, :recipient_screen_name, :recipient)
|
2052
|
+
Geo = Struct.new(:type, :coordinates, :geometries, :geometry, :properties, :id,
|
2053
|
+
:crs, :name, :href, :bbox, :features)
|
1897
2054
|
|
1898
2055
|
class TypableMap < Hash
|
1899
2056
|
#Roman = %w[
|
@@ -1925,8 +2082,7 @@ class TwitterIrcGateway < Net::IRC::Server::Session
|
|
1925
2082
|
if @seq.respond_to?(:shuffle!)
|
1926
2083
|
@seq.shuffle!
|
1927
2084
|
else
|
1928
|
-
seq = @seq.
|
1929
|
-
@seq.map! { seq.slice!(rand(seq.size)) }
|
2085
|
+
@seq = Array.new(@seq.size) { @seq.delete_at(rand(@seq.size)) }
|
1930
2086
|
end
|
1931
2087
|
@seq.freeze
|
1932
2088
|
else
|
@@ -2005,11 +2161,14 @@ class Hash
|
|
2005
2161
|
TwitterIrcGateway::Status.new
|
2006
2162
|
when struct_of?(TwitterIrcGateway::DM)
|
2007
2163
|
TwitterIrcGateway::DM.new
|
2164
|
+
when struct_of?(TwitterIrcGateway::Geo)
|
2165
|
+
TwitterIrcGateway::Geo.new
|
2008
2166
|
else
|
2009
2167
|
members = keys
|
2010
2168
|
members.concat TwitterIrcGateway::User.members
|
2011
2169
|
members.concat TwitterIrcGateway::Status.members
|
2012
2170
|
members.concat TwitterIrcGateway::DM.members
|
2171
|
+
members.concat TwitterIrcGateway::Geo.members
|
2013
2172
|
members.map! {|m| m.to_sym }
|
2014
2173
|
members.uniq!
|
2015
2174
|
Struct.new(*members).new
|
@@ -2020,10 +2179,10 @@ class Hash
|
|
2020
2179
|
struct
|
2021
2180
|
end
|
2022
2181
|
|
2023
|
-
# { :f
|
2024
|
-
# { "f" => "" } #=> "f="
|
2025
|
-
# { "f" => "v" } #=> "f=v"
|
2182
|
+
# { :f => "v" } #=> "f=v"
|
2026
2183
|
# { "f" => [1, 2] } #=> "f=1&f=2"
|
2184
|
+
# { "f" => "" } #=> "f="
|
2185
|
+
# { "f" => nil } #=> "f"
|
2027
2186
|
def to_query_str separator = "&"
|
2028
2187
|
inject([]) do |r, (k, v)|
|
2029
2188
|
k = URI.encode_component k.to_s
|
@@ -2049,7 +2208,7 @@ class String
|
|
2049
2208
|
/\A[&#+!][^ \007,]{1,50}\z/ === self
|
2050
2209
|
end
|
2051
2210
|
|
2052
|
-
def
|
2211
|
+
def screen_name?
|
2053
2212
|
/\A[A-Za-z0-9_]{1,15}\z/ === self
|
2054
2213
|
end
|
2055
2214
|
|
@@ -2060,21 +2219,28 @@ class String
|
|
2060
2219
|
end
|
2061
2220
|
|
2062
2221
|
module URI::Escape
|
2063
|
-
# URI.escape("あ1") #=> "%E3%81%82\xEF\xBC\x91"
|
2064
|
-
# URI("file:///4") #=> #<URI::Generic:0x9d09db0 URL:file:/4>
|
2065
|
-
# "\\d" -> "0-9" for Ruby 1.9
|
2066
2222
|
alias :_orig_escape :escape
|
2067
|
-
|
2068
|
-
|
2223
|
+
|
2224
|
+
if defined? ::RUBY_REVISION and RUBY_REVISION < 24544
|
2225
|
+
# URI.escape("あ1") #=> "%E3%81%82\xEF\xBC\x91"
|
2226
|
+
# URI("file:///4") #=> #<URI::Generic:0x9d09db0 URL:file:/4>
|
2227
|
+
# "\\d" -> "[0-9]" for Ruby 1.9
|
2228
|
+
def escape str, unsafe = %r{[^-_.!~*'()a-zA-Z0-9;/?:@&=+$,\[\]]}
|
2229
|
+
_orig_escape(str, unsafe)
|
2230
|
+
end
|
2231
|
+
alias :encode :escape
|
2069
2232
|
end
|
2070
|
-
alias :encode :escape
|
2071
2233
|
|
2072
2234
|
def encode_component str, unsafe = /[^-_.!~*'()a-zA-Z0-9 ]/
|
2073
2235
|
_orig_escape(str, unsafe).tr(" ", "+")
|
2074
2236
|
end
|
2075
2237
|
|
2076
|
-
def
|
2077
|
-
str.sub(%r{
|
2238
|
+
def rstrip str
|
2239
|
+
str.sub(%r{
|
2240
|
+
(?: ( / [^/?#()]* (?: \( [^/?#()]* \) [^/?#()]* )* ) \) [^/?#()]*
|
2241
|
+
| \.
|
2242
|
+
) \z
|
2243
|
+
}x, "\\1")
|
2078
2244
|
end
|
2079
2245
|
end
|
2080
2246
|
|