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,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