mumble-ruby 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/README.rdoc +76 -0
- data/Rakefile +2 -0
- data/lib/mumble-ruby.rb +7 -0
- data/lib/mumble-ruby/audio_stream.rb +54 -0
- data/lib/mumble-ruby/client.rb +210 -0
- data/lib/mumble-ruby/connection.rb +83 -0
- data/lib/mumble-ruby/messages.rb +344 -0
- data/lib/mumble-ruby/mumble.proto +276 -0
- data/lib/mumble-ruby/packet_data_stream.rb +157 -0
- data/lib/mumble-ruby/version.rb +3 -0
- data/mumble-ruby.gemspec +22 -0
- metadata +92 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use --create ruby-1.9.3-p0@mumble_ruby_gem
|
data/Gemfile
ADDED
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
data/lib/mumble-ruby.rb
ADDED
@@ -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
|
data/mumble-ruby.gemspec
ADDED
@@ -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: []
|