torchat 0.0.1.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/README.md +43 -0
  2. data/bin/torchatd +537 -0
  3. data/doc/protocol/broadcast.md +26 -0
  4. data/doc/protocol/groupchat.md +140 -0
  5. data/doc/protocol/latency.md +30 -0
  6. data/doc/protocol/standard.md +279 -0
  7. data/doc/protocol/typing.md +41 -0
  8. data/etc/torchat.yml +12 -0
  9. data/lib/torchat.rb +239 -0
  10. data/lib/torchat/protocol.rb +256 -0
  11. data/lib/torchat/protocol/broadcast.rb +40 -0
  12. data/lib/torchat/protocol/groupchat.rb +197 -0
  13. data/lib/torchat/protocol/latency.rb +44 -0
  14. data/lib/torchat/protocol/standard.rb +367 -0
  15. data/lib/torchat/protocol/typing.rb +36 -0
  16. data/lib/torchat/session.rb +603 -0
  17. data/lib/torchat/session/broadcast/message.rb +50 -0
  18. data/lib/torchat/session/broadcasts.rb +72 -0
  19. data/lib/torchat/session/buddies.rb +152 -0
  20. data/lib/torchat/session/buddy.rb +343 -0
  21. data/lib/torchat/session/buddy/joined_group_chat.rb +42 -0
  22. data/lib/torchat/session/buddy/joined_group_chats.rb +46 -0
  23. data/lib/torchat/session/buddy/latency.rb +54 -0
  24. data/lib/torchat/session/event.rb +79 -0
  25. data/lib/torchat/session/file_transfer.rb +173 -0
  26. data/lib/torchat/session/file_transfer/block.rb +51 -0
  27. data/lib/torchat/session/file_transfers.rb +89 -0
  28. data/lib/torchat/session/group_chat.rb +123 -0
  29. data/lib/torchat/session/group_chat/participant.rb +38 -0
  30. data/lib/torchat/session/group_chat/participants.rb +52 -0
  31. data/lib/torchat/session/group_chats.rb +74 -0
  32. data/lib/torchat/session/incoming.rb +187 -0
  33. data/lib/torchat/session/outgoing.rb +102 -0
  34. data/lib/torchat/tor.rb +107 -0
  35. data/lib/torchat/utils.rb +87 -0
  36. data/lib/torchat/version.rb +24 -0
  37. data/test/file_transfer/receiver.rb +41 -0
  38. data/test/file_transfer/sender.rb +45 -0
  39. data/test/group_chat/a.rb +37 -0
  40. data/test/group_chat/b.rb +37 -0
  41. data/test/group_chat/c.rb +57 -0
  42. data/torchat.gemspec +21 -0
  43. metadata +140 -0
@@ -0,0 +1,50 @@
1
+ #--
2
+ # Copyleft meh. [http://meh.paranoid.pk | meh@paranoici.org]
3
+ #
4
+ # This file is part of torchat for ruby.
5
+ #
6
+ # torchat for ruby is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published
8
+ # by the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # torchat for ruby is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with torchat for ruby. If not, see <http://www.gnu.org/licenses/>.
18
+ #++
19
+
20
+ class Torchat; class Session; module Broadcast
21
+
22
+ class Message
23
+ def self.parse (text)
24
+ new text, text.scan(/#([^ ]+)/)
25
+ end
26
+
27
+ attr_reader :at, :tags
28
+
29
+ def initialize (message, *tags)
30
+ @at = Time.new
31
+ @internal = message
32
+ @tags = tags.flatten.compact.uniq.map(&:to_sym)
33
+ end
34
+
35
+ def to_s
36
+ @internal
37
+ end
38
+
39
+ alias to_str to_s
40
+
41
+ def == (other)
42
+ to_s == other
43
+ end
44
+
45
+ def inspect
46
+ "#<Torchat::Broadcast::Message(#{tags.join ' '}): #{to_s}>"
47
+ end
48
+ end
49
+
50
+ end; end; end
@@ -0,0 +1,72 @@
1
+ #--
2
+ # Copyleft meh. [http://meh.paranoid.pk | meh@paranoici.org]
3
+ #
4
+ # This file is part of torchat for ruby.
5
+ #
6
+ # torchat for ruby is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published
8
+ # by the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # torchat for ruby is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with torchat for ruby. If not, see <http://www.gnu.org/licenses/>.
18
+ #++
19
+
20
+ require 'torchat/session/broadcast/message'
21
+
22
+ class Torchat; class Session
23
+
24
+ class Broadcasts < Array
25
+ attr_reader :session
26
+ attr_accessor :cooldown
27
+
28
+ def initialize (session)
29
+ @session = session
30
+
31
+ @cooldown = 360
32
+ end
33
+
34
+ def disable!; @disabled = true; end
35
+ def enable!; @disabled = false; end
36
+ def disabled?; @disabled; end
37
+ def enabled?; !disabled?; end
38
+
39
+ def send_message (message)
40
+ received(message, true)
41
+ end
42
+
43
+ def received? (message)
44
+ flush!
45
+
46
+ any? { |m| m == message }
47
+ end
48
+
49
+ def received (message, no_event = false)
50
+ return if disabled? or received? message
51
+
52
+ Broadcast::Message.parse(message).tap {|message|
53
+ push message
54
+
55
+ session.buddies.each_online {|id, buddy|
56
+ if buddy.supports? :broadcast
57
+ buddy.send_packet [:broadcast, :message], message.to_s
58
+ end
59
+ }
60
+
61
+ session.fire :broadcast, message: message unless no_event
62
+ }
63
+ end
64
+
65
+ def flush!
66
+ delete_if {|message|
67
+ (Time.now.to_i - message.at.to_i) > cooldown
68
+ }
69
+ end
70
+ end
71
+
72
+ end; end
@@ -0,0 +1,152 @@
1
+ #--
2
+ # Copyleft meh. [http://meh.paranoid.pk | meh@paranoici.org]
3
+ #
4
+ # This file is part of torchat for ruby.
5
+ #
6
+ # torchat for ruby is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published
8
+ # by the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # torchat for ruby is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with torchat for ruby. If not, see <http://www.gnu.org/licenses/>.
18
+ #++
19
+
20
+ require 'torchat/session/buddy'
21
+
22
+ class Torchat; class Session
23
+
24
+ class Buddies < Hash
25
+ attr_reader :session
26
+
27
+ def initialize (session)
28
+ @session = session
29
+ end
30
+
31
+ def has_key? (name)
32
+ name = name.id if name.is_a? Buddy
33
+
34
+ !!self[name]
35
+ end
36
+
37
+ def delete (name)
38
+ super(self[name].id) rescue nil
39
+ end
40
+
41
+ def [] (name)
42
+ begin
43
+ super(Torchat.normalize_id(name))
44
+ rescue ArgumentError
45
+ find { |a, b| name === b.name }
46
+ end
47
+ end
48
+
49
+ def []= (name, buddy)
50
+ name = begin
51
+ Torchat.normalize_id(name)
52
+ rescue ArgumentError
53
+ find { |a, b| name === b.name }.id
54
+ end
55
+
56
+ super(name, buddy)
57
+ end
58
+
59
+ def << (buddy)
60
+ self[buddy.id] = buddy
61
+ end
62
+
63
+ def each_online (&block)
64
+ each {|id, buddy|
65
+ block.call id, buddy if buddy.online?
66
+ }
67
+ end
68
+
69
+ def each_offline (&block)
70
+ each {|id, buddy|
71
+ block.call id, buddy if buddy.offline?
72
+ }
73
+ end
74
+
75
+ def add (id, ali = nil)
76
+ if buddy = self[id]
77
+ buddy.permanent!
78
+
79
+ return buddy
80
+ end
81
+
82
+ buddy = id.is_a?(Buddy) ? id : Buddy.new(session, id)
83
+
84
+ raise ArgumentError, 'you cannot add yourself' if session.id == buddy.id
85
+
86
+ buddy.permanent!
87
+ buddy.alias = ali
88
+
89
+ self << buddy
90
+
91
+ session.fire :buddy_add, buddy: buddy
92
+
93
+ buddy.connect if session.online?
94
+
95
+ buddy
96
+ end
97
+
98
+ def add_temporary (id, ali = nil)
99
+ if buddy = self[id]
100
+ return buddy
101
+ end
102
+
103
+ buddy = id.is_a?(Buddy) ? id : Buddy.new(session, id)
104
+
105
+ raise ArgumentError, 'you cannot add yourself' if session.id == buddy.id
106
+
107
+ buddy.temporary!
108
+ buddy.alias = ali
109
+
110
+ self << buddy
111
+
112
+ session.fire :add_buddy, buddy: buddy
113
+
114
+ buddy.connect if session.online?
115
+
116
+ buddy
117
+ end
118
+
119
+ def remove (id)
120
+ return unless has_key? id
121
+
122
+ buddy = if id.is_a? Buddy
123
+ delete(key(id))
124
+ else
125
+ delete(id)
126
+ end
127
+
128
+ buddy.remove!
129
+
130
+ session.fire :remove_buddy, buddy: buddy
131
+
132
+ buddy.remove_callbacks
133
+
134
+ if buddy.permanent? && buddy.online?
135
+ buddy.send_packet :remove_me
136
+
137
+ session.set_timeout 10 do
138
+ buddy.disconnect
139
+ end
140
+ else
141
+ buddy.disconnect
142
+ end
143
+
144
+ if buddy.blocked?
145
+ add_temporary(buddy.id).block!
146
+ end
147
+
148
+ buddy
149
+ end
150
+ end
151
+
152
+ end; end
@@ -0,0 +1,343 @@
1
+ #--
2
+ # Copyleft meh. [http://meh.paranoid.pk | meh@paranoici.org]
3
+ #
4
+ # This file is part of torchat for ruby.
5
+ #
6
+ # torchat for ruby is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published
8
+ # by the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # torchat for ruby is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with torchat for ruby. If not, see <http://www.gnu.org/licenses/>.
18
+ #++
19
+
20
+ require 'eventmachine'
21
+
22
+ require 'torchat/session/incoming'
23
+ require 'torchat/session/outgoing'
24
+
25
+ class Torchat; class Session
26
+
27
+ class Buddy
28
+ class Avatar
29
+ attr_writer :rgb, :alpha
30
+
31
+ def to_image
32
+ return unless @rgb
33
+
34
+ require 'chunky_png'
35
+
36
+ ChunkyPNG::Image.new(64, 64, ChunkyPNG::Color::TRANSPARENT).tap {|image|
37
+ @rgb.bytes.each_slice(3).with_index {|(r, g, b), index|
38
+ x, y = index % 64, index / 64
39
+
40
+ image[x, y] = if @alpha
41
+ ChunkyPNG::Color.rgba(r, g, b, @alpha[index])
42
+ else
43
+ ChunkyPNG::Color.rgb(r, g, b)
44
+ end
45
+ }
46
+ }
47
+ end
48
+ end
49
+
50
+ Client = Struct.new(:name, :version)
51
+
52
+ attr_reader :session, :id, :address, :avatar, :client, :tries, :last_try, :messages, :group_chats, :latency
53
+ attr_writer :status
54
+ attr_accessor :name, :description, :alias, :last_received
55
+
56
+ def port; 11009; end
57
+
58
+ def initialize (session, id, incoming = nil, outgoing = nil)
59
+ unless Torchat.valid_id?(id)
60
+ raise ArgumentError, "#{id} is an invalid onion id"
61
+ end
62
+
63
+ @session = session
64
+ @id = id[/^(.*?)(\.onion)?$/, 1]
65
+ @address = "#{@id}.onion"
66
+ @callbacks = []
67
+ @avatar = Avatar.new
68
+ @client = Client.new
69
+ @supports = []
70
+ @messages = []
71
+ @group_chats = JoinedGroupChats.new(self)
72
+ @latency = Latency.new(self)
73
+ @tries = 0
74
+ @last_try = nil
75
+ @typing = :stop
76
+
77
+ own! incoming
78
+ own! outgoing
79
+ end
80
+
81
+ def supports (*what)
82
+ @supports.concat(what).uniq!
83
+ end
84
+
85
+ def supports? (what)
86
+ @supports.include?(what.to_sym.downcase)
87
+ end
88
+
89
+ def status
90
+ online? ? @status : :offline
91
+ end
92
+
93
+ def on (what, &block)
94
+ removable = session.on what do |e|
95
+ block.call e if e.buddy == self
96
+ end
97
+
98
+ @callbacks << removable
99
+
100
+ removable
101
+ end
102
+
103
+ def on_packet (name = nil)
104
+ removable = if name
105
+ on :packet do |e|
106
+ block.call e if e.packet.type == name
107
+ end
108
+ else
109
+ on :packet, &block
110
+ end
111
+
112
+ @callbacks << removable
113
+
114
+ removable
115
+ end
116
+
117
+ def remove_callbacks
118
+ @callbacks.each(&:remove!).clear
119
+ end
120
+
121
+ def has_incoming?
122
+ !!@incoming
123
+ end
124
+
125
+ def has_outgoing?
126
+ !!@outgoing
127
+ end
128
+
129
+ def own! (what)
130
+ if what.is_a? Incoming
131
+ @incoming = what
132
+ elsif what.is_a? Outgoing
133
+ @outgoing = what
134
+ end
135
+
136
+ @incoming.owner = self if has_incoming?
137
+ @outgoing.owner = self if has_outgoing?
138
+ end
139
+
140
+ def pinged?; @pinged; end
141
+ def ping!(c); @pinged = c; end
142
+ def pong!; @pinged = false; end
143
+
144
+ def removed?; @removed; end
145
+ def remove!; @removed = true; end
146
+
147
+ def blocked?; @blocked; end
148
+ def allowed?; !@blocked; end
149
+
150
+ def block!
151
+ @blocked = true
152
+
153
+ disconnect
154
+ end
155
+
156
+ def allow!
157
+ @blocked = false
158
+
159
+ connect
160
+ end
161
+
162
+ def temporary?; @temporary; end
163
+ def permanent?; !@temporary; end
164
+ def temporary!; @temporary = true; end
165
+ def permanent!; @temporary = false; end
166
+
167
+ def typing?; @typing == :start; end
168
+ def thinking?; @typing == :thinking; end
169
+ def not_typing?; @typing == :stop; end
170
+
171
+ def typing!
172
+ return if typing?
173
+
174
+ @typing = :start
175
+
176
+ session.fire :typing, buddy: self, mode: :start
177
+ end
178
+
179
+ def thinking!
180
+ return if thinking?
181
+
182
+ @typing = :thinking
183
+
184
+ session.fire :typing, buddy: self, mode: :thinking
185
+ end
186
+
187
+ def not_typing!
188
+ return if not_typing?
189
+
190
+ @typing = :stop
191
+
192
+ session.fire :typing, buddy: self, mode: :stop
193
+ end
194
+
195
+ def send_packet (*args)
196
+ raise 'you cannot send packets yet' unless has_outgoing?
197
+
198
+ @outgoing.send_packet *args
199
+ end
200
+
201
+ def send_packet! (*args)
202
+ raise 'you cannot send packets yet' unless has_outgoing?
203
+
204
+ @outgoing.send_packet! *args
205
+ end
206
+
207
+ def send_message (text)
208
+ if offline?
209
+ @messages << text
210
+
211
+ unless @messages.empty?
212
+ on :ready do |e|
213
+ until @messages.empty?
214
+ break if offline?
215
+
216
+ send_message "[delayed] #{@messages.shift}"
217
+ end
218
+
219
+ if @messages.empty?
220
+ e.remove!
221
+ end
222
+ end
223
+ end
224
+ else
225
+ send_packet :message, text
226
+ end
227
+ end
228
+
229
+ def send_file (path)
230
+ session.file_transfers.send_file(self, path)
231
+ end
232
+
233
+ def send_blob (data)
234
+ session.file_transfers.send_blob(self, data)
235
+ end
236
+
237
+ def send_typing (mode)
238
+ return unless supports? :typing
239
+
240
+ send_packet "typing_#{mode}"
241
+ end
242
+
243
+ def online?; connected?; end
244
+ def offline?; !online?; end
245
+
246
+ def ready?; @ready; end
247
+ def ready!; @ready = true; end
248
+
249
+ def failed!
250
+ @connecting = false
251
+
252
+ session.fire :connect_failure, buddy: self
253
+ end
254
+
255
+ def connecting?; @connecting; end
256
+
257
+ def connect
258
+ return if connecting? || connected? || blocked?
259
+
260
+ @connecting = true
261
+ @tries += 1
262
+ @last_try = Time.new
263
+
264
+ EM.connect session.tor.host, session.tor.port, Outgoing do |outgoing|
265
+ outgoing.instance_variable_set :@session, session
266
+
267
+ own! outgoing
268
+ end
269
+ end
270
+
271
+ def connected?; @connected; end
272
+
273
+ def connected
274
+ return if connected?
275
+
276
+ @last_received = Protocol.packet :status, :available
277
+
278
+ @connecting = false
279
+ @connected = true
280
+
281
+ @tries = 0
282
+ @last_try = nil
283
+
284
+ ping! send_packet!(:ping, session.address).cookie
285
+
286
+ session.fire :connect, buddy: self
287
+ end
288
+
289
+ def verified?; @verified; end
290
+
291
+ def verified (incoming)
292
+ return if verified?
293
+
294
+ @verified = true
295
+
296
+ own! incoming
297
+
298
+ session.fire :verify, buddy: self
299
+
300
+ @outgoing.verification_completed
301
+ @incoming.verification_completed
302
+ end
303
+
304
+ def disconnect
305
+ return if disconnected?
306
+
307
+ @incoming.close_connection_after_writing if @incoming
308
+ @outgoing.close_connection_after_writing if @outgoing
309
+
310
+ @outgoing = nil
311
+ @incoming = nil
312
+
313
+ disconnected
314
+ end
315
+
316
+ def disconnected?; !@connected; end
317
+
318
+ def disconnected
319
+ return if disconnected?
320
+
321
+ session.fire :disconnect, buddy: self
322
+
323
+ @verified = false
324
+ @ready = false
325
+ @connecting = false
326
+ @connected = false
327
+ @tries = 0
328
+ @last_received = nil
329
+
330
+ @group_chats.clear
331
+
332
+ disconnect
333
+ end
334
+
335
+ def inspect
336
+ "#<Torchat::Buddy(#{id})#{": #{name}#{", #{description}" if description}" if name}>"
337
+ end
338
+ end
339
+
340
+ end; end
341
+
342
+ require 'torchat/session/buddy/joined_group_chats'
343
+ require 'torchat/session/buddy/latency'