mumble-ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []