net-irc 0.0.8 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|