mumble-ruby 0.0.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.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use --create ruby-1.9.3-p0@mumble_ruby_gem
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mumble-ruby.gemspec
4
+ gemspec
data/README.rdoc ADDED
@@ -0,0 +1,76 @@
1
+ mumble-ruby
2
+ http://www.github.com/perrym5/mumble-ruby
3
+
4
+ == DESCRIPTION:
5
+
6
+ Mumble-Ruby is a headless client for the Mumble VOIP application.
7
+ Mumble-Ruby provides the ability to write scripts and bots which interact with mumble servers through a simple DSL.
8
+ Mumble-Ruby also has the ability to stream raw audio from a fifo pipe (mpd) to the mumble server.
9
+ There is huge room for improvement in this library and I am willing to accept all sorts of pull requests so please do.
10
+
11
+ == INSTALL:
12
+
13
+ [sudo] gem install mumble-ruby
14
+
15
+ == REQUIREMENTS:
16
+
17
+ * Ruby >= 1.9.2
18
+ * CELT Audio Codec Library version 0.7
19
+
20
+ == BASIC USAGE:
21
+
22
+ # Create client instance for your server
23
+ cli = Mumble::Client.new('localhost', 64738, 'Mumble Bot', 'password123')
24
+ # => #<Mumble::Client:0x00000003064fe8 @host="localhost", @port=64738, @username="Mumble Bot", @password="password123", @channels={}, @users={}, @callbacks={}>
25
+
26
+ # Set up some callbacks for when you recieve text messages
27
+ # There are callbacks for every Mumble Protocol Message that a client can recieve
28
+ # For a reference on those, see the linked PDF at the bottom of the README.
29
+ cli.on_text_message do |msg|
30
+ puts msg.message
31
+ end
32
+ # => [#<Proc:0x0000000346e5f8@(irb):2>]
33
+
34
+ # Initiate the connection to the client
35
+ cli.connect
36
+ # => #<Thread:0x000000033d7388 run>
37
+
38
+ # Mute and Deafen yourself
39
+ cli.mute
40
+ cli.deafen
41
+
42
+ # Join the channel titled "Chillen" (this returns the number of bytes written to the socket)
43
+ cli.join_channel('Chillen')
44
+ # => 11
45
+
46
+ # Get a list of channels
47
+ cli.channels
48
+ # Returns a hash of channel_id: ChannelState Messages
49
+
50
+ # Join Channel using ID
51
+ cli.join_channel(0)
52
+
53
+ # Join Channel using ChannelState Message
54
+ cli.join_channel(cli.channels[0])
55
+
56
+ # Get a list of users
57
+ cli.users
58
+ # Returns a hash of session_id: UserState Messages
59
+
60
+ # Text user
61
+ cli.text_user('perrym5', "Hello there, I'm a robot!")
62
+ # => 35
63
+
64
+ # Start streaming from a FIFO queue of raw PCM data
65
+ cli.stream_raw_audio('/tmp/mpd.fifo')
66
+ # => #<Mumble::AudioStream ...>
67
+
68
+ # Safely disconnect
69
+ cli.disconnect
70
+ # => nil
71
+
72
+ == MUMBLE PROTOCOL:
73
+
74
+ The documentation for Mumble's control and voice protocol is a good reference for using this client as all of the callbacks
75
+ are based on the types of messages the Mumble uses to accomplish its tasks.
76
+ You can see it here[https://github.com/mumble-voip/mumble/raw/master/doc/mumble-protocol.pdf].
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,7 @@
1
+ require 'active_support/inflector.rb'
2
+ require 'mumble-ruby/version'
3
+ require 'mumble-ruby/messages.rb'
4
+ require 'mumble-ruby/connection.rb'
5
+ require 'mumble-ruby/client.rb'
6
+ require 'mumble-ruby/audio_stream.rb'
7
+ require 'mumble-ruby/packet_data_stream.rb'
@@ -0,0 +1,54 @@
1
+ module Mumble
2
+ class AudioStream
3
+ def initialize(type, target, encoder, file, connection)
4
+ @type = type
5
+ @target = target
6
+ @encoder = encoder
7
+ @file = File.open(file, 'rb')
8
+ @conn = connection
9
+ @seq = 0
10
+ @num_frames = 6
11
+ @compressed_size = [@encoder.vbr_rate / 800, 127].min
12
+ @pds = PacketDataStream.new
13
+
14
+ @queue = Queue.new
15
+ @producer = spawn_thread :produce
16
+ @consumer = spawn_thread :consume
17
+ end
18
+
19
+ private
20
+ def packet_header
21
+ ((@type << 5) | @target).chr
22
+ end
23
+
24
+ def produce
25
+ pcm_data = @file.read(@encoder.frame_size * 2)
26
+ @queue << @encoder.encode(pcm_data, @compressed_size)
27
+ end
28
+
29
+ def consume
30
+ @seq %= 1000000 # Keep sequence number reasonable for long runs
31
+
32
+ @pds.rewind
33
+ @seq += @num_frames
34
+ @pds.put_int @seq
35
+
36
+ @num_frames.times do |i|
37
+ frame = @queue.pop
38
+ len = frame.size
39
+ len = len | 0x80 if i < @num_frames - 1
40
+ @pds.append len
41
+ @pds.append_block frame
42
+ end
43
+
44
+ size = @pds.size
45
+ @pds.rewind
46
+ data = [packet_header, @pds.get_block(size)].flatten.join
47
+ @conn.send_udp_packet data
48
+ end
49
+
50
+ def spawn_thread(sym)
51
+ Thread.new { loop { send sym } }
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,210 @@
1
+ require 'thread'
2
+
3
+ module Mumble
4
+ class ChannelNotFound < StandardError; end
5
+ class UserNotFound < StandardError; end
6
+ class NoSupportedCodec < StandardError; end
7
+
8
+ CODEC_ALPHA = 0
9
+ CODEC_BETA = 3
10
+
11
+ class Client
12
+ attr_reader :host, :port, :username, :password, :users, :channels
13
+
14
+ def initialize(host, port=64738, username="Ruby Client", password="")
15
+ @host = host
16
+ @port = port
17
+ @username = username
18
+ @password = password
19
+ @users, @channels = {}, {}
20
+ @callbacks = Hash.new { |h, k| h[k] = [] }
21
+ end
22
+
23
+ def connect
24
+ @conn = Connection.new @host, @port
25
+ @conn.connect
26
+
27
+ create_encoder
28
+ version_exchange
29
+ authenticate
30
+ init_callbacks
31
+
32
+ @read_thread = spawn_thread :read
33
+ @ping_thread = spawn_thread :ping
34
+ end
35
+
36
+ def disconnect
37
+ @encoder.destroy
38
+ @read_thread.kill
39
+ @ping_thread.kill
40
+ @conn.disconnect
41
+ end
42
+
43
+ def me
44
+ @users[@session]
45
+ end
46
+
47
+ def current_channel
48
+ @channels[me.channel_id]
49
+ end
50
+
51
+ def stream_raw_audio(file)
52
+ raise NoSupportedCodec unless @codec
53
+ AudioStream.new(@codec, 0, @encoder, file, @conn)
54
+ end
55
+
56
+ Messages.all_types.each do |msg_type|
57
+ define_method "on_#{msg_type}" do |&block|
58
+ @callbacks[msg_type] << block
59
+ end
60
+
61
+ define_method "send_#{msg_type}" do |opts|
62
+ @conn.send_message(msg_type, opts)
63
+ end
64
+ end
65
+
66
+ def mute(bool=true)
67
+ send_user_state self_mute: bool
68
+ end
69
+
70
+ def deafen(bool=true)
71
+ send_user_state self_deaf: bool
72
+ end
73
+
74
+ def join_channel(channel)
75
+ send_user_state({
76
+ session: me.session,
77
+ channel_id: channel_id(channel)
78
+ })
79
+ end
80
+
81
+ def text_user(user, string)
82
+ send_text_message({
83
+ session: [user_session(user)],
84
+ message: string
85
+ })
86
+ end
87
+
88
+ def text_channel(channel, string)
89
+ send_text_message({
90
+ channel_id: [channel_id(channel)],
91
+ message: string
92
+ })
93
+ end
94
+
95
+ def user_stats(user)
96
+ send_user_stats session: user_session(user)
97
+ end
98
+
99
+ def find_user(name)
100
+ @users.values.find { |u| u.name == name }
101
+ end
102
+
103
+ def find_channel(name)
104
+ @channels.values.find { |u| u.name == name }
105
+ end
106
+
107
+ private
108
+ def spawn_thread(sym)
109
+ Thread.new { loop { send sym } }
110
+ end
111
+
112
+ def read
113
+ message = @conn.read_message
114
+ sym = message.class.to_s.demodulize.underscore.to_sym
115
+ run_callbacks sym, message
116
+ end
117
+
118
+ def ping
119
+ send_ping timestamp: Time.now.to_i
120
+ sleep(20)
121
+ end
122
+
123
+ def run_callbacks(sym, *args)
124
+ @callbacks[sym].each { |c| c.call *args }
125
+ end
126
+
127
+ def init_callbacks
128
+ on_server_sync do |message|
129
+ @session = message.session
130
+ end
131
+ on_channel_state do |message|
132
+ @channels[message.channel_id] = message
133
+ end
134
+ on_channel_remove do |message|
135
+ @channels.delete(message.channel_id)
136
+ end
137
+ on_user_state do |message|
138
+ @users[message.session] = message
139
+ end
140
+ on_user_remove do |message|
141
+ @users.delete(message.session)
142
+ end
143
+ on_codec_version do |message|
144
+ codec_negotiation(message.alpha, message.beta)
145
+ end
146
+ end
147
+
148
+ def create_encoder
149
+ @encoder = Celt::Encoder.new 48000, 480, 1
150
+ @encoder.prediction_request = 0
151
+ @encoder.vbr_rate = 60000
152
+ end
153
+
154
+ def version_exchange
155
+ send_version({
156
+ version: encode_version(1, 2, 3),
157
+ release: "mumble-ruby #{Mumble::VERSION}",
158
+ os: %x{uname -o}.strip,
159
+ os_version: %x{uname -v}.strip
160
+ })
161
+ end
162
+
163
+ def authenticate
164
+ send_authenticate({
165
+ username: @username,
166
+ password: @password,
167
+ celt_versions: [@encoder.bitstream_version]
168
+ })
169
+ end
170
+
171
+ def codec_negotiation(alpha, beta)
172
+ @codec = case @encoder.bitstream_version
173
+ when alpha then Mumble::CODEC_ALPHA
174
+ when beta then Mumble::CODEC_BETA
175
+ end
176
+ end
177
+
178
+ def channel_id(channel)
179
+ id = case channel
180
+ when Messages::ChannelState
181
+ channel.channel_id
182
+ when Fixnum
183
+ channel
184
+ when String
185
+ find_channel(channel).channel_id
186
+ end
187
+
188
+ raise ChannelNotFound unless @channels.has_key? id
189
+ id
190
+ end
191
+
192
+ def user_session(user)
193
+ id = case user
194
+ when Messages::ChannelState
195
+ user.session
196
+ when Fixnum
197
+ user
198
+ when String
199
+ find_user(user).session
200
+ end
201
+
202
+ raise UserNotFound unless @users.has_key? id
203
+ id
204
+ end
205
+
206
+ def encode_version(major, minor, patch)
207
+ (major << 16) | (minor << 8) | (patch & 0xFF)
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,83 @@
1
+ require 'socket'
2
+ require 'openssl'
3
+ require 'thread'
4
+
5
+ module Mumble
6
+ class Connection
7
+ def initialize(host, port)
8
+ @host = host
9
+ @port = port
10
+ @write_lock = Mutex.new
11
+ end
12
+
13
+ def connect
14
+ context = OpenSSL::SSL::SSLContext.new
15
+ context.verify_mode = OpenSSL::SSL::VERIFY_NONE
16
+ tcp_sock = TCPSocket.new @host, @port
17
+ @sock = OpenSSL::SSL::SSLSocket.new tcp_sock, context
18
+ @sock.connect
19
+ end
20
+
21
+ def disconnect
22
+ @sock.sysclose
23
+ end
24
+
25
+ def read_message
26
+ header = read_data 6
27
+ type, len = header.unpack Messages::HEADER_FORMAT
28
+ data = read_data len
29
+ if type == message_type(:udp_tunnel)
30
+ # UDP Packet -- No Protobuf
31
+ message = message_class(:udp_tunnel).new
32
+ message.packet = data
33
+ else
34
+ message = message_raw type, data
35
+ end
36
+ message
37
+ end
38
+
39
+ def send_udp_packet(packet)
40
+ header = [message_type(:udp_tunnel), packet.length].pack Messages::HEADER_FORMAT
41
+ send_data(header + packet)
42
+ end
43
+
44
+ def send_message(sym, attrs)
45
+ type, klass = message(sym)
46
+ message = klass.new
47
+ attrs.each { |k, v| message.send("#{k}=", v) }
48
+ serial = message.serialize_to_string
49
+ header = [type, serial.size].pack Messages::HEADER_FORMAT
50
+ send_data(header + serial)
51
+ end
52
+
53
+ private
54
+ def send_data(data)
55
+ @write_lock.synchronize do
56
+ @sock.syswrite data
57
+ end
58
+ end
59
+
60
+ def read_data(len)
61
+ @sock.sysread len
62
+ end
63
+
64
+ def message(obj)
65
+ return message_type(obj), message_class(obj)
66
+ end
67
+
68
+ def message_type(obj)
69
+ if obj.is_a? Protobuf::Message
70
+ obj = obj.class.to_s.demodulize.underscore.to_sym
71
+ end
72
+ Messages.sym_to_type(obj)
73
+ end
74
+
75
+ def message_class(obj)
76
+ Messages.type_to_class(message_type(obj))
77
+ end
78
+
79
+ def message_raw(type, data)
80
+ Messages.raw_to_obj(type, data)
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,344 @@
1
+ ### Generated by rprotoc. DO NOT EDIT!
2
+ ### <proto file: Mumble.proto>
3
+
4
+ require 'protobuf/message/message'
5
+ require 'protobuf/message/enum'
6
+ require 'protobuf/message/service'
7
+ require 'protobuf/message/extend'
8
+
9
+ module Mumble
10
+ module Messages
11
+ ::Protobuf::OPTIONS[:optimize_for] = :SPEED
12
+ HEADER_FORMAT = "nN"
13
+
14
+ @@sym_to_type = {
15
+ version: 0,
16
+ udp_tunnel: 1,
17
+ authenticate: 2,
18
+ ping: 3,
19
+ reject: 4,
20
+ server_sync: 5,
21
+ channel_remove: 6,
22
+ channel_state: 7,
23
+ user_remove: 8,
24
+ user_state: 9,
25
+ ban_list: 10,
26
+ text_message: 11,
27
+ permission_denied: 12,
28
+ acl: 13,
29
+ query_users: 14,
30
+ crypt_setup: 15,
31
+ context_action_add: 16,
32
+ context_action: 17,
33
+ user_list: 18,
34
+ voice_target: 19,
35
+ permission_query: 20,
36
+ codec_version: 21,
37
+ user_stats: 22,
38
+ request_blob: 23,
39
+ server_config: 24
40
+ }
41
+
42
+ @@type_to_sym = @@sym_to_type.invert
43
+
44
+ class << self
45
+ def all_types
46
+ return @@sym_to_type.keys
47
+ end
48
+
49
+ def sym_to_type(sym)
50
+ @@sym_to_type[sym]
51
+ end
52
+
53
+ def type_to_class(type)
54
+ const_get(@@type_to_sym[type].to_s.camelcase)
55
+ end
56
+
57
+ def raw_to_obj(type, data)
58
+ message = type_to_class(type).new
59
+ message.parse_from_string(data)
60
+ message
61
+ end
62
+ end
63
+
64
+ class Version < ::Protobuf::Message
65
+ defined_in __FILE__
66
+ optional :uint32, :version, 1
67
+ optional :string, :release, 2
68
+ optional :string, :os, 3
69
+ optional :string, :os_version, 4
70
+ end
71
+ class UdpTunnel < ::Protobuf::Message
72
+ defined_in __FILE__
73
+ required :bytes, :packet, 1
74
+ end
75
+ class Authenticate < ::Protobuf::Message
76
+ defined_in __FILE__
77
+ optional :string, :username, 1
78
+ optional :string, :password, 2
79
+ repeated :string, :tokens, 3
80
+ repeated :int32, :celt_versions, 4
81
+ end
82
+ class Ping < ::Protobuf::Message
83
+ defined_in __FILE__
84
+ optional :uint64, :timestamp, 1
85
+ optional :uint32, :good, 2
86
+ optional :uint32, :late, 3
87
+ optional :uint32, :lost, 4
88
+ optional :uint32, :resync, 5
89
+ optional :uint32, :udp_packets, 6
90
+ optional :uint32, :tcp_packets, 7
91
+ optional :float, :udp_ping_avg, 8
92
+ optional :float, :udp_ping_var, 9
93
+ optional :float, :tcp_ping_avg, 10
94
+ optional :float, :tcp_ping_var, 11
95
+ end
96
+ class Reject < ::Protobuf::Message
97
+ defined_in __FILE__
98
+ class RejectType < ::Protobuf::Enum
99
+ defined_in __FILE__
100
+ None = value(:None, 0)
101
+ WrongVersion = value(:WrongVersion, 1)
102
+ InvalidUsername = value(:InvalidUsername, 2)
103
+ WrongUserPW = value(:WrongUserPW, 3)
104
+ WrongServerPW = value(:WrongServerPW, 4)
105
+ UsernameInUse = value(:UsernameInUse, 5)
106
+ ServerFull = value(:ServerFull, 6)
107
+ NoCertificate = value(:NoCertificate, 7)
108
+ end
109
+ optional :RejectType, :type, 1
110
+ optional :string, :reason, 2
111
+ end
112
+ class ServerConfig < ::Protobuf::Message
113
+ defined_in __FILE__
114
+ optional :uint32, :max_bandwidth, 1
115
+ optional :string, :welcome_text, 2
116
+ optional :bool, :allow_html, 3
117
+ optional :uint32, :message_length, 4
118
+ optional :uint32, :image_message_length, 5
119
+ end
120
+ class ServerSync < ::Protobuf::Message
121
+ defined_in __FILE__
122
+ optional :uint32, :session, 1
123
+ optional :uint32, :max_bandwidth, 2
124
+ optional :string, :welcome_text, 3
125
+ optional :uint64, :permissions, 4
126
+ end
127
+ class ChannelRemove < ::Protobuf::Message
128
+ defined_in __FILE__
129
+ required :uint32, :channel_id, 1
130
+ end
131
+ class ChannelState < ::Protobuf::Message
132
+ defined_in __FILE__
133
+ optional :uint32, :channel_id, 1
134
+ optional :uint32, :parent, 2
135
+ optional :string, :name, 3
136
+ repeated :uint32, :links, 4
137
+ optional :string, :description, 5
138
+ repeated :uint32, :links_add, 6
139
+ repeated :uint32, :links_remove, 7
140
+ optional :bool, :temporary, 8, :default => false
141
+ optional :int32, :position, 9, :default => 0
142
+ optional :bytes, :description_hash, 10
143
+ end
144
+ class UserRemove < ::Protobuf::Message
145
+ defined_in __FILE__
146
+ required :uint32, :session, 1
147
+ optional :uint32, :actor, 2
148
+ optional :string, :reason, 3
149
+ optional :bool, :ban, 4
150
+ end
151
+ class UserState < ::Protobuf::Message
152
+ defined_in __FILE__
153
+ optional :uint32, :session, 1
154
+ optional :uint32, :actor, 2
155
+ optional :string, :name, 3
156
+ optional :uint32, :user_id, 4
157
+ optional :uint32, :channel_id, 5
158
+ optional :bool, :mute, 6
159
+ optional :bool, :deaf, 7
160
+ optional :bool, :suppress, 8
161
+ optional :bool, :self_mute, 9
162
+ optional :bool, :self_deaf, 10
163
+ optional :bytes, :texture, 11
164
+ optional :bytes, :plugin_context, 12
165
+ optional :string, :plugin_identity, 13
166
+ optional :string, :comment, 14
167
+ optional :string, :hash, 15
168
+ optional :bytes, :comment_hash, 16
169
+ optional :bytes, :texture_hash, 17
170
+ optional :bool, :priority_speaker, 18
171
+ optional :bool, :recording, 19
172
+ end
173
+ class BanList < ::Protobuf::Message
174
+ defined_in __FILE__
175
+ class BanEntry < ::Protobuf::Message
176
+ defined_in __FILE__
177
+ required :bytes, :address, 1
178
+ required :uint32, :mask, 2
179
+ optional :string, :name, 3
180
+ optional :string, :hash, 4
181
+ optional :string, :reason, 5
182
+ optional :string, :start, 6
183
+ optional :uint32, :duration, 7
184
+ end
185
+ repeated :BanEntry, :bans, 1
186
+ optional :bool, :query, 2, :default => false
187
+ end
188
+ class TextMessage < ::Protobuf::Message
189
+ defined_in __FILE__
190
+ optional :uint32, :actor, 1
191
+ repeated :uint32, :session, 2
192
+ repeated :uint32, :channel_id, 3
193
+ repeated :uint32, :tree_id, 4
194
+ required :string, :message, 5
195
+ end
196
+ class PermissionDenied < ::Protobuf::Message
197
+ defined_in __FILE__
198
+ class DenyType < ::Protobuf::Enum
199
+ defined_in __FILE__
200
+ Text = value(:Text, 0)
201
+ Permission = value(:Permission, 1)
202
+ SuperUser = value(:SuperUser, 2)
203
+ ChannelName = value(:ChannelName, 3)
204
+ TextTooLong = value(:TextTooLong, 4)
205
+ H9K = value(:H9K, 5)
206
+ TemporaryChannel = value(:TemporaryChannel, 6)
207
+ MissingCertificate = value(:MissingCertificate, 7)
208
+ UserName = value(:UserName, 8)
209
+ ChannelFull = value(:ChannelFull, 9)
210
+ end
211
+ optional :uint32, :permission, 1
212
+ optional :uint32, :channel_id, 2
213
+ optional :uint32, :session, 3
214
+ optional :string, :reason, 4
215
+ optional :DenyType, :type, 5
216
+ optional :string, :name, 6
217
+ end
218
+ class Acl < ::Protobuf::Message
219
+ defined_in __FILE__
220
+ class ChanGroup < ::Protobuf::Message
221
+ defined_in __FILE__
222
+ required :string, :name, 1
223
+ optional :bool, :inherited, 2, :default => true
224
+ optional :bool, :inherit, 3, :default => true
225
+ optional :bool, :inheritable, 4, :default => true
226
+ repeated :uint32, :add, 5
227
+ repeated :uint32, :remove, 6
228
+ repeated :uint32, :inherited_members, 7
229
+ end
230
+ class ChanACL < ::Protobuf::Message
231
+ defined_in __FILE__
232
+ optional :bool, :apply_here, 1, :default => true
233
+ optional :bool, :apply_subs, 2, :default => true
234
+ optional :bool, :inherited, 3, :default => true
235
+ optional :uint32, :user_id, 4
236
+ optional :string, :group, 5
237
+ optional :uint32, :grant, 6
238
+ optional :uint32, :deny, 7
239
+ end
240
+ required :uint32, :channel_id, 1
241
+ optional :bool, :inherit_acls, 2, :default => true
242
+ repeated :ChanGroup, :groups, 3
243
+ repeated :ChanACL, :acls, 4
244
+ optional :bool, :query, 5, :default => false
245
+ end
246
+ class QueryUsers < ::Protobuf::Message
247
+ defined_in __FILE__
248
+ repeated :uint32, :ids, 1
249
+ repeated :string, :names, 2
250
+ end
251
+ class CryptSetup < ::Protobuf::Message
252
+ defined_in __FILE__
253
+ optional :bytes, :key, 1
254
+ optional :bytes, :client_nonce, 2
255
+ optional :bytes, :server_nonce, 3
256
+ end
257
+ class ContextActionAdd < ::Protobuf::Message
258
+ defined_in __FILE__
259
+ class Context < ::Protobuf::Enum
260
+ defined_in __FILE__
261
+ Server = value(:Server, 1)
262
+ Channel = value(:Channel, 2)
263
+ User = value(:User, 4)
264
+ end
265
+ required :string, :action, 1
266
+ required :string, :text, 2
267
+ optional :uint32, :context, 3
268
+ end
269
+ class ContextAction < ::Protobuf::Message
270
+ defined_in __FILE__
271
+ optional :uint32, :session, 1
272
+ optional :uint32, :channel_id, 2
273
+ required :string, :action, 3
274
+ end
275
+ class UserList < ::Protobuf::Message
276
+ defined_in __FILE__
277
+ class User < ::Protobuf::Message
278
+ defined_in __FILE__
279
+ required :uint32, :user_id, 1
280
+ optional :string, :name, 2
281
+ end
282
+ repeated :User, :users, 1
283
+ end
284
+ class VoiceTarget < ::Protobuf::Message
285
+ defined_in __FILE__
286
+ class Target < ::Protobuf::Message
287
+ defined_in __FILE__
288
+ repeated :uint32, :session, 1
289
+ optional :uint32, :channel_id, 2
290
+ optional :string, :group, 3
291
+ optional :bool, :links, 4, :default => false
292
+ optional :bool, :children, 5, :default => false
293
+ end
294
+ optional :uint32, :id, 1
295
+ repeated :Target, :targets, 2
296
+ end
297
+ class PermissionQuery < ::Protobuf::Message
298
+ defined_in __FILE__
299
+ optional :uint32, :channel_id, 1
300
+ optional :uint32, :permissions, 2
301
+ optional :bool, :flush, 3, :default => false
302
+ end
303
+ class CodecVersion < ::Protobuf::Message
304
+ defined_in __FILE__
305
+ required :int32, :alpha, 1
306
+ required :int32, :beta, 2
307
+ required :bool, :prefer_alpha, 3, :default => true
308
+ end
309
+ class UserStats < ::Protobuf::Message
310
+ defined_in __FILE__
311
+ class Stats < ::Protobuf::Message
312
+ defined_in __FILE__
313
+ optional :uint32, :good, 1
314
+ optional :uint32, :late, 2
315
+ optional :uint32, :lost, 3
316
+ optional :uint32, :resync, 4
317
+ end
318
+ optional :uint32, :session, 1
319
+ optional :bool, :stats_only, 2, :default => false
320
+ repeated :bytes, :certificates, 3
321
+ optional :Stats, :from_client, 4
322
+ optional :Stats, :from_server, 5
323
+ optional :uint32, :udp_packets, 6
324
+ optional :uint32, :tcp_packets, 7
325
+ optional :float, :udp_ping_avg, 8
326
+ optional :float, :udp_ping_var, 9
327
+ optional :float, :tcp_ping_avg, 10
328
+ optional :float, :tcp_ping_var, 11
329
+ optional :Version, :version, 12
330
+ repeated :int32, :celt_versions, 13
331
+ optional :bytes, :address, 14
332
+ optional :uint32, :bandwidth, 15
333
+ optional :uint32, :onlinesecs, 16
334
+ optional :uint32, :idlesecs, 17
335
+ optional :bool, :strong_certificate, 18, :default => false
336
+ end
337
+ class RequestBlob < ::Protobuf::Message
338
+ defined_in __FILE__
339
+ repeated :uint32, :session_texture, 1
340
+ repeated :uint32, :session_comment, 2
341
+ repeated :uint32, :channel_description, 3
342
+ end
343
+ end
344
+ end
@@ -0,0 +1,276 @@
1
+ package MumbleProto;
2
+
3
+ option optimize_for = SPEED;
4
+
5
+ message Version {
6
+ optional uint32 version = 1;
7
+ optional string release = 2;
8
+ optional string os = 3;
9
+ optional string os_version = 4;
10
+ }
11
+
12
+ message UDPTunnel {
13
+ required bytes packet = 1;
14
+ }
15
+
16
+ message Authenticate {
17
+ optional string username = 1;
18
+ optional string password = 2;
19
+ repeated string tokens = 3;
20
+ repeated int32 celt_versions = 4;
21
+ }
22
+
23
+ message Ping {
24
+ optional uint64 timestamp = 1;
25
+ optional uint32 good = 2;
26
+ optional uint32 late = 3;
27
+ optional uint32 lost = 4;
28
+ optional uint32 resync = 5;
29
+ optional uint32 udp_packets = 6;
30
+ optional uint32 tcp_packets = 7;
31
+ optional float udp_ping_avg = 8;
32
+ optional float udp_ping_var = 9;
33
+ optional float tcp_ping_avg = 10;
34
+ optional float tcp_ping_var = 11;
35
+ }
36
+
37
+ message Reject {
38
+ enum RejectType {
39
+ None = 0;
40
+ WrongVersion = 1;
41
+ InvalidUsername = 2;
42
+ WrongUserPW = 3;
43
+ WrongServerPW = 4;
44
+ UsernameInUse = 5;
45
+ ServerFull = 6;
46
+ NoCertificate = 7;
47
+ }
48
+ optional RejectType type = 1;
49
+ optional string reason = 2;
50
+ }
51
+
52
+ message ServerConfig {
53
+ optional uint32 max_bandwidth = 1;
54
+ optional string welcome_text = 2;
55
+ optional bool allow_html = 3;
56
+ optional uint32 message_length = 4;
57
+ optional uint32 image_message_length = 5;
58
+ }
59
+
60
+ message ServerSync {
61
+ optional uint32 session = 1;
62
+ optional uint32 max_bandwidth = 2;
63
+ optional string welcome_text = 3;
64
+ optional uint64 permissions = 4;
65
+ }
66
+
67
+ message ChannelRemove {
68
+ required uint32 channel_id = 1;
69
+ }
70
+
71
+ message ChannelState {
72
+ optional uint32 channel_id = 1;
73
+ optional uint32 parent = 2;
74
+ optional string name = 3;
75
+ repeated uint32 links = 4;
76
+ optional string description = 5;
77
+ repeated uint32 links_add = 6;
78
+ repeated uint32 links_remove = 7;
79
+ optional bool temporary = 8 [default = false];
80
+ optional int32 position = 9 [default = 0];
81
+ optional bytes description_hash = 10;
82
+ }
83
+
84
+ message UserRemove {
85
+ required uint32 session = 1;
86
+ optional uint32 actor = 2;
87
+ optional string reason = 3;
88
+ optional bool ban = 4;
89
+ }
90
+
91
+ message UserState {
92
+ optional uint32 session = 1;
93
+ optional uint32 actor = 2;
94
+ optional string name = 3;
95
+ optional uint32 user_id = 4;
96
+ optional uint32 channel_id = 5;
97
+ optional bool mute = 6;
98
+ optional bool deaf = 7;
99
+ optional bool suppress = 8;
100
+ optional bool self_mute = 9;
101
+ optional bool self_deaf = 10;
102
+ optional bytes texture = 11;
103
+ optional bytes plugin_context = 12;
104
+ optional string plugin_identity = 13;
105
+ optional string comment = 14;
106
+ optional string hash = 15;
107
+ optional bytes comment_hash = 16;
108
+ optional bytes texture_hash = 17;
109
+ optional bool priority_speaker = 18;
110
+ optional bool recording = 19;
111
+ }
112
+
113
+ message BanList {
114
+ message BanEntry {
115
+ required bytes address = 1;
116
+ required uint32 mask = 2;
117
+ optional string name = 3;
118
+ optional string hash = 4;
119
+ optional string reason = 5;
120
+ optional string start = 6;
121
+ optional uint32 duration = 7;
122
+ }
123
+ repeated BanEntry bans = 1;
124
+ optional bool query = 2 [default = false];
125
+ }
126
+
127
+ message TextMessage {
128
+ optional uint32 actor = 1;
129
+ repeated uint32 session = 2;
130
+ repeated uint32 channel_id = 3;
131
+ repeated uint32 tree_id = 4;
132
+ required string message = 5;
133
+ }
134
+
135
+ message PermissionDenied {
136
+ enum DenyType {
137
+ Text = 0;
138
+ Permission = 1;
139
+ SuperUser = 2;
140
+ ChannelName = 3;
141
+ TextTooLong = 4;
142
+ H9K = 5;
143
+ TemporaryChannel = 6;
144
+ MissingCertificate = 7;
145
+ UserName = 8;
146
+ ChannelFull = 9;
147
+ }
148
+ optional uint32 permission = 1;
149
+ optional uint32 channel_id = 2;
150
+ optional uint32 session = 3;
151
+ optional string reason = 4;
152
+ optional DenyType type = 5;
153
+ optional string name = 6;
154
+ }
155
+
156
+ message ACL {
157
+ message ChanGroup {
158
+ required string name = 1;
159
+ optional bool inherited = 2 [default = true];
160
+ optional bool inherit = 3 [default = true];
161
+ optional bool inheritable = 4 [default = true];
162
+ repeated uint32 add = 5;
163
+ repeated uint32 remove = 6;
164
+ repeated uint32 inherited_members = 7;
165
+ }
166
+ message ChanACL {
167
+ optional bool apply_here = 1 [default = true];
168
+ optional bool apply_subs = 2 [default = true];
169
+ optional bool inherited = 3 [default = true];
170
+ optional uint32 user_id = 4;
171
+ optional string group = 5;
172
+ optional uint32 grant = 6;
173
+ optional uint32 deny = 7;
174
+ }
175
+ required uint32 channel_id = 1;
176
+ optional bool inherit_acls = 2 [default = true];
177
+ repeated ChanGroup groups = 3;
178
+ repeated ChanACL acls = 4;
179
+ optional bool query = 5 [default = false];
180
+ }
181
+
182
+ message QueryUsers {
183
+ repeated uint32 ids = 1;
184
+ repeated string names = 2;
185
+ }
186
+
187
+ message CryptSetup {
188
+ optional bytes key = 1;
189
+ optional bytes client_nonce = 2;
190
+ optional bytes server_nonce = 3;
191
+ }
192
+
193
+ message ContextActionAdd {
194
+ enum Context {
195
+ Server = 0x01;
196
+ Channel = 0x02;
197
+ User = 0x04;
198
+ }
199
+ required string action = 1;
200
+ required string text = 2;
201
+ optional uint32 context = 3;
202
+ }
203
+
204
+ message ContextAction {
205
+ optional uint32 session = 1;
206
+ optional uint32 channel_id = 2;
207
+ required string action = 3;
208
+ }
209
+
210
+ message UserList {
211
+ message User {
212
+ required uint32 user_id = 1;
213
+ optional string name = 2;
214
+ }
215
+ repeated User users = 1;
216
+ }
217
+
218
+ message VoiceTarget {
219
+ message Target {
220
+ repeated uint32 session = 1;
221
+ optional uint32 channel_id = 2;
222
+ optional string group = 3;
223
+ optional bool links = 4 [default = false];
224
+ optional bool children = 5 [default = false];
225
+ }
226
+ optional uint32 id = 1;
227
+ repeated Target targets = 2;
228
+ }
229
+
230
+ message PermissionQuery {
231
+ optional uint32 channel_id = 1;
232
+ optional uint32 permissions = 2;
233
+ optional bool flush = 3 [default = false];
234
+ }
235
+
236
+ message CodecVersion {
237
+ required int32 alpha = 1;
238
+ required int32 beta = 2;
239
+ required bool prefer_alpha = 3 [default = true];
240
+ }
241
+
242
+ message UserStats {
243
+ message Stats {
244
+ optional uint32 good = 1;
245
+ optional uint32 late = 2;
246
+ optional uint32 lost = 3;
247
+ optional uint32 resync = 4;
248
+ }
249
+
250
+ optional uint32 session = 1;
251
+ optional bool stats_only = 2 [default = false];
252
+ repeated bytes certificates = 3;
253
+ optional Stats from_client = 4;
254
+ optional Stats from_server = 5;
255
+
256
+ optional uint32 udp_packets = 6;
257
+ optional uint32 tcp_packets = 7;
258
+ optional float udp_ping_avg = 8;
259
+ optional float udp_ping_var = 9;
260
+ optional float tcp_ping_avg = 10;
261
+ optional float tcp_ping_var = 11;
262
+
263
+ optional Version version = 12;
264
+ repeated int32 celt_versions = 13;
265
+ optional bytes address = 14;
266
+ optional uint32 bandwidth = 15;
267
+ optional uint32 onlinesecs = 16;
268
+ optional uint32 idlesecs = 17;
269
+ optional bool strong_certificate = 18 [default = false];
270
+ }
271
+
272
+ message RequestBlob {
273
+ repeated uint32 session_texture = 1;
274
+ repeated uint32 session_comment = 2;
275
+ repeated uint32 channel_description = 3;
276
+ }
@@ -0,0 +1,157 @@
1
+ module Mumble
2
+ class PacketDataStream
3
+ def initialize(data=nil)
4
+ @data = data || 0.chr * 1024
5
+ @data = @data.split ''
6
+ @pos = 0
7
+ @ok = true
8
+ @capacity = @data.size
9
+ end
10
+
11
+ def valid
12
+ @ok
13
+ end
14
+
15
+ def size
16
+ @pos
17
+ end
18
+
19
+ def left
20
+ @capacity - @pos
21
+ end
22
+
23
+ def append(val)
24
+ if @pos < @capacity
25
+ @data[@pos] = val.chr
26
+ skip
27
+ else
28
+ @ok = false
29
+ end
30
+ end
31
+
32
+ def append_block(data)
33
+ len = data.size
34
+ if len < left
35
+ @data[@pos, len] = data.split('')
36
+ skip len
37
+ else
38
+ @ok = false
39
+ end
40
+ end
41
+
42
+ def get_block(len)
43
+ if len < left
44
+ ret = @data[@pos, len]
45
+ skip len
46
+ else
47
+ @ok = false
48
+ ret = []
49
+ end
50
+ ret
51
+ end
52
+
53
+ def get_next
54
+ if @pos < @capacity
55
+ ret = @data[@pos].ord
56
+ skip
57
+ else
58
+ ret = 0
59
+ @ok = false
60
+ end
61
+ ret
62
+ end
63
+
64
+ def rewind
65
+ @pos = 0
66
+ end
67
+
68
+ def skip(len=1)
69
+ len < left ? @pos += len : @ok = false
70
+ end
71
+
72
+ def put_int(int)
73
+ if !(int & 0x8000000000000000).zero? && (~int < 0x100000000)
74
+ int = ~int
75
+ puts int
76
+ if int <= 0x3
77
+ # Shortcase for -1 to -4
78
+ append(0xFC | int)
79
+ else
80
+ append(0xF8)
81
+ end
82
+ end
83
+
84
+ if int < 0x80
85
+ # Need top bit clear
86
+ append(int)
87
+ elsif int < 0x4000
88
+ # Need top two bits clear
89
+ append((int >> 8) | 0x80)
90
+ append(int & 0xFF)
91
+ elsif int < 0x200000
92
+ # Need top three bits clear
93
+ append((int >> 16) | 0xC0)
94
+ append((int >> 8) & 0xFF)
95
+ append(int & 0xFF)
96
+ elsif int < 0x10000000
97
+ # Need top four bits clear
98
+ append((int >> 24) | 0xE0)
99
+ append((int >> 16) & 0xFF)
100
+ append((int >> 8) & 0xFF)
101
+ append(int & 0xFF)
102
+ elsif int < 0x100000000
103
+ # It's a full 32-bit integer.
104
+ append(0xF0)
105
+ append((int >> 24) & 0xFF)
106
+ append((int >> 16) & 0xFF)
107
+ append((int >> 8) & 0xFF)
108
+ append(int & 0xFF)
109
+ else
110
+ # It's a 64-bit value.
111
+ append(0xF4)
112
+ append((int >> 56) & 0xFF)
113
+ append((int >> 48) & 0xFF)
114
+ append((int >> 40) & 0xFF)
115
+ append((int >> 32) & 0xFF)
116
+ append((int >> 24) & 0xFF)
117
+ append((int >> 16) & 0xFF)
118
+ append((int >> 8) & 0xFF)
119
+ append(int & 0xFF)
120
+ end
121
+ end
122
+
123
+ def get_int
124
+ v = get_next
125
+ int = 0
126
+
127
+ if (v & 0x80) == 0x00
128
+ int = v & 0x7F
129
+ elsif (v & 0xC0) == 0x80
130
+ int = (v & 0x3F) << 8 | get_next
131
+ elsif (v & 0xF0) == 0xF0
132
+ x = v & 0xFC
133
+ if x == 0xF0
134
+ int = get_next << 24 | get_next << 16 | get_next << 8 | get_next
135
+ elsif x == 0xF4
136
+ int = get_next << 56 | get_next << 48 | get_next << 40 | get_next << 32 |
137
+ get_next << 24 | get_next << 16 | get_next << 8 | get_next
138
+ elsif x == 0xF8
139
+ int = get_int
140
+ int = ~int
141
+ elsif x == 0xFC
142
+ int = v & 0x03
143
+ int = ~int
144
+ else
145
+ @ok = false
146
+ int = 0
147
+ end
148
+ elsif (v & 0xF0) == 0xE0
149
+ int = (v & 0x0F) << 24 | get_next << 16 | get_next << 8 | get_next
150
+ elsif (v & 0xE0) == 0xC0
151
+ int = (v & 0x1F) << 16 | get_next << 8 | get_next
152
+ end
153
+
154
+ return int
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,3 @@
1
+ module Mumble
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/mumble-ruby/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Matthew Perry"]
6
+ gem.email = ["perrym5@rpi.edu"]
7
+ gem.description = %q{Ruby API for interacting with a mumble server}
8
+ gem.summary = %q{Implements the mumble VOIP protocol in ruby for more easily writing clients.}
9
+ gem.homepage = "http://www.github.com/perrym5/mumble-ruby"
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = "mumble-ruby"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Mumble::VERSION
17
+
18
+ gem.add_dependency "activesupport"
19
+ gem.add_dependency "celt-ruby"
20
+
21
+ gem.add_development_dependency "ruby_protobuf"
22
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mumble-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Matthew Perry
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: &11094520 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *11094520
25
+ - !ruby/object:Gem::Dependency
26
+ name: celt-ruby
27
+ requirement: &11093120 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *11093120
36
+ - !ruby/object:Gem::Dependency
37
+ name: ruby_protobuf
38
+ requirement: &11091560 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *11091560
47
+ description: Ruby API for interacting with a mumble server
48
+ email:
49
+ - perrym5@rpi.edu
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .gitignore
55
+ - .rvmrc
56
+ - Gemfile
57
+ - README.rdoc
58
+ - Rakefile
59
+ - lib/mumble-ruby.rb
60
+ - lib/mumble-ruby/audio_stream.rb
61
+ - lib/mumble-ruby/client.rb
62
+ - lib/mumble-ruby/connection.rb
63
+ - lib/mumble-ruby/messages.rb
64
+ - lib/mumble-ruby/mumble.proto
65
+ - lib/mumble-ruby/packet_data_stream.rb
66
+ - lib/mumble-ruby/version.rb
67
+ - mumble-ruby.gemspec
68
+ homepage: http://www.github.com/perrym5/mumble-ruby
69
+ licenses: []
70
+ post_install_message:
71
+ rdoc_options: []
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubyforge_project:
88
+ rubygems_version: 1.8.10
89
+ signing_key:
90
+ specification_version: 3
91
+ summary: Implements the mumble VOIP protocol in ruby for more easily writing clients.
92
+ test_files: []