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,41 @@
1
+ Typing notice extension lifecycle and packet description
2
+ ========================================================
3
+ This is a simple extension to support typing notice.
4
+
5
+ They're simply packets sent to the buddy you're talking to to tell them your current
6
+ typing status.
7
+
8
+ Packets
9
+ -------
10
+ Following is a list and description of the packets in the groupchat extension.
11
+
12
+ In the examples `>` refers to packet sent to the buddy, while `<` refers to packet received
13
+ from the buddy.
14
+
15
+ ### Typing Start
16
+
17
+ This packet is used to tell the other end we started typing.
18
+
19
+ ```
20
+ > typing_start
21
+ ```
22
+
23
+ ### Typing Thinking
24
+
25
+ This packet is used to tell the other end we stopped typing momentarily to think.
26
+
27
+ This packet can be not sent at all, it's just a fancy addition because BitlBee supports it.
28
+
29
+ ```
30
+ > typing_thinking
31
+ ```
32
+
33
+ ### Typing Stop
34
+
35
+ This packet is used to tell the other end we stopped typing.
36
+
37
+ This packet can avoid being sent if the message is sent.
38
+
39
+ ```
40
+ > typing_stop
41
+ ```
@@ -0,0 +1,12 @@
1
+ ---
2
+ name: Anonymous
3
+ description: This is a description.
4
+
5
+ connection:
6
+ outgoing:
7
+ host: 127.0.0.1
8
+ port: 11108
9
+
10
+ incoming:
11
+ host: 127.0.0.1
12
+ port: 11008
@@ -0,0 +1,239 @@
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 'yaml'
21
+ require 'iniparse'
22
+ require 'fileutils'
23
+
24
+ require 'torchat/version'
25
+ require 'torchat/utils'
26
+ require 'torchat/session'
27
+ require 'torchat/protocol'
28
+ require 'torchat/tor'
29
+
30
+ class Torchat
31
+ def self.profile (name = nil)
32
+ FileUtils.mkpath(directory = File.expand_path("~/.torchat#{"_#{name}" if name}"))
33
+
34
+ new("#{directory}/torchat.ini").tap {|t|
35
+ t.name = name
36
+ t.path = directory
37
+ }
38
+ end
39
+
40
+ attr_reader :config, :session, :tor, :path
41
+ attr_accessor :name
42
+
43
+ def initialize (path)
44
+ @config = if path.is_a? Hash
45
+ path
46
+ elsif path.end_with?('ini')
47
+ ini = IniParse.parse(File.read(File.expand_path(path)))
48
+
49
+ {
50
+ 'id' => ini[:client][:own_hostname],
51
+ 'name' => ini[:profile][:name],
52
+ 'description' => ini[:profile][:text],
53
+
54
+ 'connection' => {
55
+ 'outgoing' => {
56
+ 'host' => ini[:tor_portable][:tor_server],
57
+ 'port' => ini[:tor_portable][:tor_server_socks_port]
58
+ },
59
+
60
+ 'incoming' => {
61
+ 'host' => ini[:client][:listen_interface],
62
+ 'port' => ini[:client][:listen_port]
63
+ }
64
+ }
65
+ }
66
+ else
67
+ YAML.parse_file(path).transform
68
+ end
69
+
70
+ if @config['path']
71
+ if @config['path'].is_a? String
72
+ self.path = @config['path']
73
+ else
74
+ if @config['path']['buddy_list']
75
+ buddy_list_at @config['path']['buddy_list']
76
+ end
77
+
78
+ if @config['path']['blocked_buddy_list']
79
+ blocked_buddy_list_at @config['path']['blocked_buddy_list']
80
+ end
81
+
82
+ if @config['path']['offline_messages']
83
+ offline_messages_at @config['path']['offline_messages']
84
+ end
85
+ end
86
+ end
87
+
88
+ @tor = Tor.new(@config)
89
+ end
90
+
91
+ def method_missing (id, *args, &block)
92
+ return @session.__send__ id, *args, &block if @session.respond_to? id
93
+
94
+ super
95
+ end
96
+
97
+ def respond_to_missing? (id, include_private = false)
98
+ @session.respond_to? id, include_private
99
+ end
100
+
101
+ def start (&block)
102
+ @session = Session.new(@config, &block)
103
+
104
+ if @config['buddies']
105
+ @config['buddies'].each {|id|
106
+ @session.buddies.add(id)
107
+ }
108
+ end
109
+
110
+ if @buddy_list && File.readable?(@buddy_list)
111
+ File.open(@buddy_list).lines.each {|line|
112
+ line.match(/^(.+?)(?: (.*?))?$/) {|m|
113
+ next if m[1] == @session.id
114
+
115
+ @session.buddies.add(m[1], m[2])
116
+ }
117
+ }
118
+ end
119
+
120
+ if @blocked_buddy_list && File.readable?(@blocked_buddy_list)
121
+ File.open(@blocked_buddy_list).lines.each {|line|
122
+ line.match(/^(.+?)(?: (.*?))?$/) {|m|
123
+ next if m[1] == @session.id
124
+
125
+ @session.buddies.add_temporary(m[1], m[2]).block!
126
+ }
127
+ }
128
+ end
129
+
130
+ if @offline_messages
131
+ Dir["#@offline_messages/*_offline.txt"].each {|path|
132
+ unless buddy = @session.buddies[path[/([^\/_]+)_offline\.txt$/, 1]]
133
+ File.delete(path)
134
+
135
+ next
136
+ end
137
+
138
+ File.open(path) {|f|
139
+ current = ''
140
+
141
+ until f.eof?
142
+ line = f.readline
143
+
144
+ if line.start_with? '[delayed] '
145
+ buddy.messages.push current
146
+
147
+ current = line[10 .. -1]
148
+ else
149
+ current << "\n" << line
150
+ end
151
+ end
152
+
153
+ buddy.messages.push current unless current.empty?
154
+ }
155
+
156
+ File.delete(path)
157
+ }
158
+ end
159
+
160
+ @session.start
161
+ end
162
+
163
+ def stop
164
+ @session.stop
165
+ @tor.stop
166
+
167
+ if @buddy_list
168
+ File.open(@buddy_list, 'w') {|f|
169
+ @session.buddies.each {|id, buddy|
170
+ next if buddy.temporary?
171
+
172
+ f.puts "#{id} #{buddy.alias || buddy.name}"
173
+ }
174
+ }
175
+ end
176
+
177
+ if @blocked_buddy_list
178
+ File.open(@blocked_buddy_list, 'w') {|f|
179
+ @session.buddies.each {|id, buddy|
180
+ next unless buddy.blocked?
181
+
182
+ f.puts "#{id} #{buddy.alias || buddy.name}"
183
+ }
184
+ }
185
+ end
186
+
187
+ if @offline_messages
188
+ @session.buddies.each {|id, buddy|
189
+ next if buddy.messages.empty?
190
+
191
+ File.open("#@offline_messages/#{id}_offline.txt") {|f|
192
+ buddy.messages.each {|message|
193
+ f.write "[delayed] #{message}\n"
194
+ }
195
+ }
196
+ }
197
+ end
198
+ end
199
+
200
+ def path= (directory)
201
+ @path = directory
202
+
203
+ unless @buddy_list
204
+ buddy_list_at "#{directory}/buddy-list.txt"
205
+ end
206
+
207
+ unless @blocked_buddy_list
208
+ blocked_buddy_list_at "#{directory}/blocked-buddy-list.txt"
209
+ end
210
+
211
+ unless @offline_messages
212
+ offline_messages_at directory
213
+ end
214
+ end
215
+
216
+ def buddy_list_at (path)
217
+ @buddy_list = File.expand_path(path)
218
+ end
219
+
220
+ def blocked_buddy_list_at (path)
221
+ @blocked_buddy_list = File.expand_path(path)
222
+ end
223
+
224
+ def offline_messages_at (path)
225
+ @offline_messages = File.expand_path(path)
226
+ end
227
+
228
+ def send_packet_to (name, packet)
229
+ @session.buddies[name].send_packet(packet)
230
+ end
231
+
232
+ def send_message_to (name, message)
233
+ @session.buddies[name].send_message(message)
234
+ end
235
+
236
+ def send_broadcast (message)
237
+ @session.broadcasts.send_message(message)
238
+ end
239
+ end
@@ -0,0 +1,256 @@
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; module Protocol
21
+
22
+ def self.encode (data)
23
+ data = data.dup
24
+
25
+ data.force_encoding 'BINARY'
26
+ data.gsub!("\\", "\\/")
27
+ data.gsub!("\n", "\\n")
28
+
29
+ data
30
+ end
31
+
32
+ def self.decode (data)
33
+ data = data.dup
34
+
35
+ data.force_encoding 'BINARY'
36
+ data.gsub!("\\n", "\n")
37
+ data.gsub!("\\/", "\\")
38
+
39
+ data
40
+ end
41
+
42
+ @packets = Hash.new { |h, k| h[k] = {} }
43
+ @extensions = []
44
+
45
+ def self.[] (extension = nil, name)
46
+ extension = extension.to_sym.downcase if extension
47
+ name = name.to_sym.downcase
48
+
49
+ @packets[extension][name]
50
+ end
51
+
52
+ def self.[]= (extension = nil, name, value)
53
+ extension = extension.to_sym.downcase if extension
54
+ name = name.to_sym.downcase
55
+
56
+ if value.nil?
57
+ @packets[extension].delete(name)
58
+ else
59
+ @packets[extension][name] = value
60
+ end
61
+ end
62
+
63
+ def self.has_packet? (extension = nil, name)
64
+ extension = extension.to_sym.downcase if extension
65
+ name = name.to_sym.downcase
66
+
67
+ @packets[extension].has_key?(name)
68
+ end
69
+
70
+ def self.packets (extension = nil)
71
+ if extension
72
+ @packets[extension].values
73
+ else
74
+ @packets.map { |extension, packets|
75
+ packets.map { |name, packet|
76
+ packet
77
+ }
78
+ }.flatten
79
+ end
80
+ end
81
+
82
+ def self.extensions
83
+ @extensions.map {|name|
84
+ Struct.new(:name, :packets).new(name, packets(name))
85
+ }
86
+ end
87
+
88
+ def self.define_packet (name, &block)
89
+ raise ArgumentError, "#{name} already exists" if has_packet?(@extension, name)
90
+
91
+ self[@extension, name] = Packet.define(name, @extension, &block)
92
+ end
93
+
94
+ def self.define_packet! (name, &block)
95
+ self[@extension, name] = nil
96
+
97
+ define_packet(name, &block)
98
+ end
99
+
100
+ def self.define_extension (name)
101
+ @extensions.push(name).uniq!
102
+
103
+ tmp, @extension = @extension, name
104
+ result = yield
105
+ @extension = tmp
106
+
107
+ result
108
+ end
109
+
110
+ def self.packet (*args)
111
+ if args.first.is_a? Packet
112
+ args.first
113
+ else
114
+ unless packet = self[*args.first]
115
+ raise ArgumentError, "#{args.first.inspect} packet unknown"
116
+ end
117
+
118
+ args.shift
119
+
120
+ packet.new(*args)
121
+ end
122
+ end
123
+
124
+ def self.unpack (data, from = nil)
125
+ name, data = data.chomp.split(' ', 2)
126
+
127
+ unless packet = self[name] || self[*name.split('_', 2)]
128
+ raise ArgumentError, "#{name} packet unknown"
129
+ end
130
+
131
+ unless packet.respond_to? :unpack
132
+ raise ArgumentError, "#{name} packet has no unpacker"
133
+ end
134
+
135
+ packet = packet.unpack(data ? decode(data) : nil)
136
+ packet.from = from
137
+
138
+ packet
139
+ end
140
+
141
+ class Packet
142
+ def self.define (name, extension = nil, &block)
143
+ Class.new(self, &block).tap {|c|
144
+ c.instance_eval {
145
+ define_singleton_method :type do name end
146
+ define_method :type do name end
147
+
148
+ define_singleton_method :extension do extension end
149
+ define_method :extension do extension end
150
+
151
+ define_singleton_method :inspect do
152
+ "#<Torchat::Packet: #{"#{extension}_" if extension}#{type}>"
153
+ end
154
+ }
155
+ }
156
+ end
157
+
158
+ def self.define_unpacker (&block)
159
+ define_singleton_method :unpack do |data|
160
+ new(*block.call(data))
161
+ end
162
+ end
163
+
164
+ def self.define_unpacker_for (range, &block)
165
+ unless range.is_a? Range
166
+ range = range .. range
167
+ end
168
+
169
+ if block
170
+ define_unpacker &block
171
+ else
172
+ define_unpacker do |data|
173
+ if data.nil? || data.empty?
174
+ if range.begin == 0 || range.end == 0
175
+ next
176
+ else
177
+ raise ArgumentError, "wrong number of arguments (0 for #{range.begin})"
178
+ end
179
+ end
180
+
181
+ args = range.end == -1 ? data.split(' ') : data.split(' ', range.end)
182
+ arity = range.end == -1 ? range.begin .. args.length : range
183
+
184
+ if args.last && args.last.empty?
185
+ args[-1] = nil
186
+ end
187
+
188
+ unless arity === args.length
189
+ raise ArgumentError, "wrong number of arguments (#{args.length} for #{args.length < range.begin ? range.begin : range.end})"
190
+ end
191
+
192
+ args
193
+ end
194
+ end
195
+
196
+ if range == (0 .. 0)
197
+ define_method :pack do
198
+ super('')
199
+ end
200
+
201
+ define_method :inspect do
202
+ "#<Torchat::Packet[#{"#{extension}_" if extension}#{type}]>"
203
+ end
204
+ elsif range.end == 1
205
+ if range.begin == 0
206
+ define_method :initialize do |value = nil|
207
+ @internal = value
208
+ end
209
+ else
210
+ define_method :initialize do |value|
211
+ @internal = value
212
+ end
213
+ end
214
+
215
+ define_method :pack do
216
+ super(@internal.to_s)
217
+ end
218
+
219
+ define_method :nil? do
220
+ @internal.nil?
221
+ end
222
+
223
+ define_method :inspect do
224
+ "#<Torchat::Packet[#{"#{extension}_" if extension}#{type}]#{": #{@internal.inspect}" if @internal}>"
225
+ end
226
+ else
227
+ define_method :initialize do |*args|
228
+ @internal = args
229
+ end
230
+
231
+ define_method :inspect do
232
+ "#<Torchat::Packet[#{"#{extension}_" if extension}#{type}]: #{@internal.map(&:inspect).join(', ')}>"
233
+ end
234
+ end
235
+ end
236
+
237
+ def self.new (*args, &block)
238
+ super(*args, &block).tap {|packet|
239
+ packet.at = Time.new
240
+ }
241
+ end
242
+
243
+ attr_accessor :from, :at
244
+
245
+ def pack (data)
246
+ "#{"#{extension}_" if extension}#{type}#{" #{Protocol.encode(data)}" if data}\n"
247
+ end
248
+ end
249
+
250
+ require 'torchat/protocol/standard'
251
+ require 'torchat/protocol/groupchat'
252
+ require 'torchat/protocol/typing'
253
+ require 'torchat/protocol/broadcast'
254
+ require 'torchat/protocol/latency'
255
+
256
+ end; end