torchat 0.0.1.rc.1

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.
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'