diff --git a/README.md b/README.md
index 7972afc..33e6887 100644
--- a/README.md
+++ b/README.md
@@ -1,292 +1,190 @@
-# Important Announcement
+# Bragi
-Hello everyone,
+## About the Name
-First, let's look at the problems:
-1. I don't use mumble anymore, working on a bot you don't use produces a leak of testing and motivation.
-2. I don't code like before, my hobbies have changed, I maintain stuff I still use, but no real coding anymore.
-3. Botamusique is monolitique
+Bragi is named after the Norse god of poetry, music, and eloquence. In Norse mythology, Bragi was known for his wisdom and skill with words, and was considered the patron of poets and musicians. This makes him the perfect namesake for a Mumble music bot that brings music and audio content to voice channels.
-I've been trying to make a POC to change the monolitique part, to have a fully modulable bot, with asyncio and and feature/backend as plugins. But asyncio was blocking for me, especially to make the bot with fastapi, discord api / pymumble. It's 2 async loop and I don't have the knowledge to make it work.
-To be transparent, botamusique was the biggest project I've done, one of the funniest. Thanks @TerryGeng for joining the adventure.
-
-I don't think I will be looking for a maintainer, the monolithic part of this project is not something that needs to be maintained.
-
-**This projet will be archived.**
-
-BUT If someone want to rewrite a bot, I'm ready to help with the projet : what to do, Errors to avoid, Design/architecture help (but no code). I think **_8 years_** on this projet (have start with [this small projet](https://github.com/azlux/MumbleRadioPlayer/commit/56ca276c5519fcb0e1af043beb043202e65c2cca)) can help someone.
-
-It was really funny, thank all, for your support !
-
-See you in space cowboy.
-
--- Azlux
-
------
+---
-
-
botamusique
+
🎵 Bragi - Mumble Music Bot 🎵
-Botamusique is a [Mumble](https://www.mumble.info/) music bot.
-Predicted functionalities will be those people would expect from any classic music player.
-
-[](https://ci.azlux.fr/azlux/botamusique)
+Bragi is a streamlined [Mumble](https://www.mumble.info/) music bot forked from the original botamusique project. Designed as a self-contained, lightweight solution focused on core music playback functionality without unnecessary complexity.
## Features
1. **Support multiple music sources:**
- - Music files in local folders (which can be uploaded through the web interface).
- - Youtube/Soundcloud URLs and playlists (everything supported by youtube-dl).
- - Radio stations from URL and http://www.radio-browser.info API.
-2. **Modern and powerful web remote control interface.** Powered by Flask. Which supports:
- - Playlist management.
- - Music library management, including uploading, browsing all files and edit tags, etc.
-3. **Powerful command system.** Commands and words the bot says are fully customizable. Support partial-match for commands.
-4. **Ducking.** The bot would automatically lower its volume if people are talking.
-5. **Stereo sound.** After Mumble 1.4.0, stereo output support has been added. Our bot is designed to work nicely with it naturally.
-6. **Multilingual support.** A list of supported languages can be found below.
+ - Music files in local folders
+ - YouTube/SoundCloud URLs and playlists (via yt-dlp)
+ - Radio stations from URL and radio-browser.info API
+2. **Text-based command interface** - Control the bot entirely through Mumble text commands
+3. **Powerful command system** - Commands are fully customizable with partial-match support
+4. **Ducking** - Automatically lowers volume when people are talking
+5. **Stereo sound** - Full stereo output support for Mumble 1.4.0+
+6. **Python 3.13 compatible** - Modern Python support with integrated dependencies
+## Design Philosophy
-## Screenshots
+Bragi focuses on being a **self-contained, lightweight Mumble music bot** without unnecessary complexity. The codebase prioritizes:
+- Core music playback functionality
+- Text-based command interface only
+- Minimal external dependencies
+- Direct Python execution
-
-
-
-
------
## Quick Start Guide
+
1. [Installation](#installation)
-1. [Configuration](#configuration)
-1. [Run the bot](#run-the-bot)
-1. [Operate the bot](#operate-the-bot)
-1. [Update](#update)
-1. [Known issues](#known-issues)
-1. [Contributors](#contributors)
+2. [Configuration](#configuration)
+3. [Run the bot](#run-the-bot)
+4. [Commands](#commands)
## Installation
### Dependencies
-1. Install python. We require a python version of 3.6 or higher.
-1. Install [Opus Codec](https://www.opus-codec.org/) (which should be already installed if you installed Mumble or Murmur, or you may try to install `opus-tools` with your package manager).
-1. Install ffmpeg. If ffmpeg isn't in your package manager, you may need to find another source. I personally use [this repository](http://repozytorium.mati75.eu/) on my raspberry.
+1. **Python 3.6 or higher** (Python 3.13 recommended)
+2. **Opus Codec** - Usually installed with Mumble, or install `opus-tools` with your package manager
+3. **ffmpeg** - For audio processing
-### Docker
-See https://github.com/azlux/botamusique/wiki/Docker-install
+### Install Bragi
-Both stable and nightly (developing) builds are available!
-
-### Manual install
-
-**Stable release (recommended)**
-
-This is current stable version, with auto-update support. To install the stable release, run these lines in your terminal:
-```
-curl -Lo botamusique.tar.gz http://packages.azlux.fr/botamusique/sources-stable.tar.gz
-tar -xzf botamusique.tar.gz
-cd botamusique
+**From source:**
+```bash
+git clone https://git.stormux.org/storm/bragi
+cd bragi
python3 -m venv venv
venv/bin/pip install wheel
venv/bin/pip install -r requirements.txt
```
-**Nightly build (developing version)**
-
- Click to expand!
-
-This build reflects any newest change in the master branch, with auto-update support baked in. This version follow all commits into the master branch.
+**Required system packages (Arch Linux example):**
+```bash
+sudo pacman -S opus ffmpeg python-magic
```
-curl -Lo botamusique.tar.gz http://packages.azlux.fr/botamusique/sources-testing.tar.gz
-tar -xzf botamusique.tar.gz
-cd botamusique
-python3 -m venv venv
-venv/bin/pip install wheel
-venv/bin/pip install -r requirements.txt
-```
-
-
-**Build from source code**
-
- Click to expand!
-
-You can checkout the master branch of our repo and compile everything by yourself.
-We will test new features in the master branch, maybe sometimes post some hotfixes.
-Please be noted that the builtin auto-update support doesn't track this version.
-If you have no idea what these descriptions mean to you, we recommend you install the stable version above.
-```
-git clone https://github.com/azlux/botamusique.git
-cd botamusique
-python3 -m venv venv
-venv/bin/pip install wheel
-venv/bin/pip install -r requirements.txt
-(cd web && npm install && npm run build)
-venv/bin/python3 ./scripts/translate_templates.py --lang-dir lang/ --template-dir web/templates/
-```
-
## Configuration
-Please copy `configuration.example.ini` into `configuration.ini`, follow the instructions in that file and uncomment options you would like to modify. Not all sections are needed. You may just keep the options that matter to you. For example, if you only would like to set `host`, all you need you is keep
-```
+
+1. Copy `configuration.example.ini` to `configuration.ini`
+2. Edit the configuration file with your server details:
+
+```ini
[server]
-host=xxxxxx
+host = your.mumble.server
+port = 64738
+channel = Music
+
+[bot]
+username = Bragi
+music_folder = /path/to/your/music
```
-in your `configuration.ini`.
-Please DO NOT MODIFY `configuration.default.ini`, since if the bot realizes one option is undefined in `configuration.ini`, it will look into `configuration.default.ini` for the default value of that option. This file will be constantly overridden in each update.
+**DO NOT MODIFY** `configuration.default.ini` - it contains default values that may be overridden during updates.
-We list some basic settings for you to quickly get things working.
+### Basic Settings
-### Basic settings
-1. Usually, the first thing is to set the Murmur server you'd like the bot to connect to. You may also specify which channel the bot stays, and tokens used by the bot.
-```
+**Server Connection:**
+```ini
[server]
host = 127.0.0.1
port = 64738
+channel = Music
```
-2. You need to specify a folder that stores your music files. The bot will look for music and upload files into that folder. You also need to specify a temporary folder to store music file downloads from URLs.
-```
+**Bot Configuration:**
+```ini
[bot]
+username = Bragi
music_folder = music_folder/
tmp_folder = /tmp/
+volume = 0.5
```
-3. **Web interface is disabled by default** for performance and security reasons. It is extremely powerful, so we encourage you to have a try. To enable it, set
-```
-[webinterface]
-enabled = True
+**Generate Certificate (Recommended):**
+```bash
+openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout bragi.pem -out bragi.pem -subj "/CN=bragi"
```
-Default binding address is
-```
-listening_addr = 127.0.0.1
-listening_port = 8181
-```
-
-You can access the web interface through http://127.0.0.1:8181 if you keep it unchanged.
-
-Note: Listening to address `127.0.0.1` will only accept requests from localhost. _If you would like to connect from the public internet, you need to set it to `0.0.0.0`, and set up username and password to impose access control._ In addition, if the bot is behind a router, you should also properly set forwarding rules in you NAT configuration to forward requests to the bot.
-
-4. The default language is English, but you can change it in `[bot]` section:
-```
-[bot]
-language=en_US
-```
-
-Available translations can be found inside `lang/` folder. Currently, options are
-
- - `en_US`, English
- - `es_ES`, Spanish
- - `fr_FR`, French
- - `it_IT`, Italian
- - `ja_JP`, Japanese
- - `zh_CN`, Chinese
-
-5. Generate a certificate (Optional, but recommended)
-
-By default, murmur server uses certificates to identify users. Without a valid certificate, you wouldn't able to register the bot into your Murmur server. Some server even refused users without a certificate. Therefore, it is recommended to generate a certificate for the bot. If you have a certificate (for say, `botmusique.pem` in the folder of the bot), you can specify its location in
-```
+Then add to configuration:
+```ini
[server]
-certificate=botamusique.pem
+certificate = bragi.pem
```
-If you don't have a certificate, you may generate one by:
-`openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout botamusique.pem -out botamusique.pem -subj "/CN=botamusique"`
-
-
-### Sections explained
-- `server`: configuration about the server. Will be overridden by the `./mumbleBot.py` parameters.
-- `bot`: basic configuration of the bot, eg. name, comment, folder, default volume, etc.
-- `webinterface`: basic configuration about the web interface.
-- `commands`: you can customize the command you want for each action (eg. put `help = helpme` , the bot will respond to `!helpme`)
-- `radio`: a list of default radio (eg. play a jazz radio with the command `!radio jazz`)
-- `debug`: option to activate ffmpeg or pymumble debug output.
-
-
## Run the bot
-If you have set up everything in your `configuration.ini`, you can
-`venv/bin/python mumbleBot.py --config configuration.ini`
-Or you can
-`venv/bin/python mumbleBot.py -s HOST -u BOTNAME -P PASSWORD -p PORT -c CHANNEL -C /path/to/botamusique.pem`
-
-If you want information about auto-starting and auto-restarting of the bot, you can check out the wiki page [Run botamusique as a daemon In the background](https://github.com/azlux/botamusique/wiki/Run-botamusique-as-a-daemon-In-the-background).
-
-**For the detailed manual of using botamusique, please see the [wiki](https://github.com/azlux/botamusique/wiki).**
-
-## Operate the bot
-
-You can control the bot by both commands sent by text message and the web interface.
-
-By default, all commands start with `!`. You can type `!help` in the text message to see the full list of commands supported, or see the examples on the [wiki page](https://github.com/azlux/botamusique/wiki/Command-Help-and-Examples).
-
-The web interface can be used if you'd like an intuitive way of interacting with the bot. Through it is fairly straightforward, a walk-through can be found on the [wiki page](https://github.com/azlux/botamusique/wiki/Web-interface-walk-through).
-
-## Update
-
-If you enable `auto_check_update`, the bot will check for updates every time it starts.
-If you are using the recommended install, you can send `!update` to the bot (command by default).
-
-If you are using git, you need to update manually:
-```
-git pull --all
-git submodule update
-venv/bin/pip install --upgrade -r requirements.txt
-```
-
-
-## Known issues
-
-1. During installation, you may encounter the following error:
-```
-ImportError: libtiff.so.5: cannot open shared object file: No such file or directory
-```
-You need to install a missing library: `apt install libtiff5`
-
-2. In the beginning, you may encounter the following error even if you have installed all requirements:
-```
-Exception: Could not find opus library. Make sure it is installed.
-```
-You need to install the opus codec (not embedded in all system): `apt install libopus0`
-
-3. MacOS Users may encounter the following error:
-```
-ImportError: failed to find libmagic. Check your installation
-```
-This is caused by missing `libmagic` binaries and can be solved by
+**With configuration file:**
```bash
-brew install libmagic
-
+venv/bin/python mumbleBot.py --config configuration.ini
```
-One may also install `python-magic-bin` instead of `python-magic`.
-5. If you have a large amount of music files (>1000), it may take some time for the bot to boot, since
-it will build up the cache for the music library on booting. You may want to disable this auto-scanning by
-setting ``refresh_cache_on_startup=False`` in `[bot]` section and control the scanning manually by
-``!rescan`` command and the *Rescan Files* button on the web interface.
-
-6. Alpine Linux requires some extra dependencies during the installation (in order to compile Pillow):
+**With command line arguments:**
+```bash
+venv/bin/python mumbleBot.py -s HOST -u Bragi -P PASSWORD -p PORT -c CHANNEL -C /path/to/bragi.pem
```
-python3-dev musl-lib libmagic jpeg-dev zlib-dev gcc
-```
-For more information, see [#122](https://github.com/azlux/botamusique/issues/122).
-## _I need help!_
+## Commands
-If you ran into some problems in using the bot, or discovered bugs and want to talk to us, you may
+All commands start with `!` by default. Type `!help` in Mumble chat for a full list.
- - Start a new issue,
- - Ask in the Matrix channel of Mumble [#mumble:matrix.org](https://matrix.to/#/#mumble:matrix.org) (we are usually there to help).
+**Basic Controls:**
+- `!play ` - Play a file or URL
+- `!pause` - Pause playback
+- `!stop` - Stop playback
+- `!skip` - Skip current track
+- `!volume <0-100>` - Set volume
-## Contributors
-If you want to help us develop, you're welcome to fork and submit pull requests (fixes and new features).
-We are looking for people helping us translating the bot. If you'd like to add a new language or fix errors in existed translations,
-feel free to catch us in the IRC channel #mumble, or just email us!
+**File Management:**
+- `!file ` - Search for local files
+- `!queue` - Show current playlist
+- `!clear` - Clear playlist
-The following people joined as collaborators for a faster development, big thanks to them:
-- @TerryGeng
-- @mertkutay
+**Radio:**
+- `!radio ` - Play radio station
+- `!rb ` - Search radio-browser.info
-Feel free to ask me if you want to help actively without using pull requests.
+**Admin Commands:**
+- `!kill` - Shut down the bot (admin only)
+- `!refresh` - Refresh music cache (admin only)
+
+## Configuration Sections
+
+- `[server]` - Mumble server connection settings
+- `[bot]` - Basic bot configuration (name, folders, volume, etc.)
+- `[commands]` - Customize command names
+- `[radio]` - Default radio stations
+- `[debug]` - Enable debug output for troubleshooting
+
+## Known Issues
+
+1. **Missing opus library:**
+ ```
+ Exception: Could not find opus library. Make sure it is installed.
+ ```
+ Install opus: `sudo pacman -S opus` (Arch) or `apt install libopus0` (Debian/Ubuntu)
+
+2. **Missing libmagic:**
+ ```
+ ImportError: failed to find libmagic. Check your installation
+ ```
+ Install magic: `sudo pacman -S python-magic` (Arch) or `apt install python3-magic` (Debian/Ubuntu)
+
+3. **Large music libraries (>1000 files)** may take time to scan on startup. Disable with:
+ ```ini
+ [bot]
+ refresh_cache_on_startup = False
+ ```
+ Then use `!refresh` command manually.
+
+## Credits
+
+Bragi is forked from [botamusique](https://github.com/azlux/botamusique) by azlux.
+
+Original contributors:
+- azlux - Original botamusique creator
+- TerryGeng - Major contributor
+- mertkutay - Collaborator
+
+## License
+
+This project maintains the same license as the original botamusique project.
diff --git a/command.py b/command.py
index be5f4d3..89932cc 100644
--- a/command.py
+++ b/command.py
@@ -3,6 +3,10 @@
# Bragi - A Mumble music bot
# Forked from botamusique by azlux (https://github.com/azlux/botamusque)
#
+import os
+# Fix protobuf compatibility issue with Python 3.13
+os.environ.setdefault('PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION', 'python')
+
import logging
import secrets
import datetime
@@ -28,7 +32,6 @@ log = logging.getLogger("bot")
def register_all_commands(bot):
bot.register_command(commands('add_from_shortlist'), cmd_shortlist)
bot.register_command(commands('add_tag'), cmd_add_tag)
- bot.register_command(commands('change_user_password'), cmd_user_password, no_partial_match=True)
bot.register_command(commands('clear'), cmd_clear)
bot.register_command(commands('current_music'), cmd_current_music)
bot.register_command(commands('delete_from_library'), cmd_delete_from_library)
diff --git a/configuration.default.ini b/configuration.default.ini
index a5f6399..6f73e39 100644
--- a/configuration.default.ini
+++ b/configuration.default.ini
@@ -87,7 +87,6 @@ user_agent =
[commands]
add_from_shortlist = shortlist, sl
add_tag = addtag
-change_user_password = password
clear = clear
command_symbol = !:!
current_music = np, now
diff --git a/configuration.example.ini b/configuration.example.ini
index 164f865..ff9bb63 100644
--- a/configuration.example.ini
+++ b/configuration.example.ini
@@ -23,7 +23,7 @@ port = 64738
# 'username': The bot's username.
# 'comment': Comment displayed on the bot's profile.
# 'avatar': Path to an image used for the bot's avatar (PNG recommended, 128 KB max).
-#username = bragi
+#username = Bragi
#comment = "Hi, I'm here to play radio, local music or youtube/soundcloud music. Have fun!"
#avatar =
@@ -74,9 +74,7 @@ port = 64738
#autoplay_length = 5
#clear_when_stop_in_oneshot = False
-# 'target_version': version to fetch when updating:
-# stable: use the curl command to get stable releases
-# testing: follow git master branch using the git command
+# Auto-update system has been removed from Bragi
#target_version = stable
# 'tmp_folder': Folder that music will be downloaded into.
@@ -91,9 +89,8 @@ port = 64738
# 'download_attempts': How many times to attempt a download.
#download_attempts = 2
-# 'auto_check_update': Whether to check for updates every time the bot starts,
-# and post the changelog after an update was applied.
-#auto_check_update = True
+# Auto-update system has been removed from Bragi
+#auto_check_update = False
#pip3_path = venv/bin/pip
# 'logfile': File to write log messages to.
@@ -158,12 +155,12 @@ port = 64738
# Web interface has been removed from Bragi
-# The [debug] section contains settings to enable debugging messaages.
+# The [debug] section contains settings to enable debugging messages.
[debug]
# 'ffmpeg': Whether to display debug messages from ffmpeg.
# 'mumble_connection': Whether to display debug messages for the
# connection to the Mumble server (from the pymumble library).
-# 'youtube_dl': Whether to display debug messages from youtube-dl.
+# 'youtube_dl': Whether to display debug messages from yt-dlp.
#ffmpeg = False
#mumble_connection = False
#youtube_dl = False
diff --git a/mumbleBot.py b/mumbleBot.py
index dea93c8..9072561 100644
--- a/mumbleBot.py
+++ b/mumbleBot.py
@@ -4,6 +4,10 @@
# Bragi - A Mumble music bot
# Forked from botamusique by azlux (https://github.com/azlux/botamusque)
#
+import os
+# Fix protobuf compatibility issue with Python 3.13
+os.environ.setdefault('PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION', 'python')
+
import re
import threading
import time
@@ -14,7 +18,6 @@ import configparser
import audioop
import subprocess as sp
import argparse
-import os
import os.path
import pymumble_py3 as pymumble
import pymumble_py3.constants
@@ -123,6 +126,18 @@ class MumbleBot:
self.mumble.callbacks.set_callback(pymumble.constants.PYMUMBLE_CLBK_TEXTMESSAGERECEIVED, self.message_received)
self.mumble.set_codec_profile("audio")
+
+ # Enable sound output for music playback - need to recreate since it was set to None
+ self.mumble.receive_sound = True
+ from pymumble_py3 import soundoutput
+ self.mumble.sound_output = soundoutput.SoundOutput(
+ self.mumble,
+ pymumble.constants.PYMUMBLE_AUDIO_PER_PACKET,
+ self.bandwidth, # Use bot's bandwidth setting instead of mumble's (not yet initialized)
+ stereo=self.mumble.stereo,
+ opus_profile=self.mumble.get_codec_profile()
+ )
+
self.mumble.start() # start the mumble thread
self.mumble.is_ready() # wait for the connection
diff --git a/pymumble_py3/__init__.py b/pymumble_py3/__init__.py
new file mode 100644
index 0000000..0a1f96e
--- /dev/null
+++ b/pymumble_py3/__init__.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+#
+# pymumble_py3 - Integrated from https://github.com/azlux/pymumble
+# This is a complete copy of the pymumble library integrated into Bragi
+# to avoid external dependency issues after the original project maintenance stopped.
+#
+import os
+# Fix protobuf compatibility issue with Python 3.13
+os.environ.setdefault('PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION', 'python')
+
+from .mumble import Mumble
diff --git a/pymumble_py3/acl.py b/pymumble_py3/acl.py
new file mode 100644
index 0000000..9823cd9
--- /dev/null
+++ b/pymumble_py3/acl.py
@@ -0,0 +1,140 @@
+# -*- coding: utf-8 -*-
+import time
+
+from .errors import ACLChanGroupNotExist
+from threading import Lock
+from . import messages
+
+
+class ACL(dict):
+ def __init__(self, mumble_object, channel_id):
+ self.mumble_object = mumble_object
+ self.channel_id = channel_id # channel id attached to the ACLS
+ self.inherit_acls = False
+ self.groups = {}
+ self.acls = {}
+ self.lock = Lock()
+
+ def update(self, message):
+ self.lock.acquire()
+ self.inherit_acls = bool(message.inherit_acls)
+ for msg_group in message.groups:
+ if msg_group.name in self.groups:
+ self.groups[msg_group.name].update(msg_group)
+ else:
+ self.groups[msg_group.name] = ChanGroup()
+ self.groups[msg_group.name].update(msg_group)
+ for msg_acl in message.acls:
+ if msg_acl.group in self.acls:
+ self.acls[msg_acl.group].update(msg_acl)
+ else:
+ self.acls[msg_acl.group] = ChanACL()
+ self.acls[msg_acl.group].update(msg_acl)
+ self.lock.release()
+
+ def request_group_update(self, group_name):
+ if group_name not in self.groups:
+ self.mumble_object.channels[self.channel_id].request_acl()
+ i = 0
+ while group_name not in self.groups and i < 20:
+ time.sleep(0.2)
+ i += 1
+ if i == 20:
+ raise ACLChanGroupNotExist(group_name)
+
+ def add_user(self, group_name, user_id):
+ self.request_group_update(group_name)
+ if user_id not in self.groups[group_name].add:
+ self.groups[group_name].add.append(user_id)
+ self.send_update()
+
+ def del_user(self, group_name, user_id):
+ self.request_group_update(group_name)
+ self.groups[group_name].add.remove(user_id)
+ self.send_update()
+
+ def add_remove_user(self, group_name, user_id):
+ self.request_group_update(group_name)
+ if user_id not in self.groups[group_name].remove:
+ self.groups[group_name].remove.append(user_id)
+ self.send_update()
+
+ def del_remove_user(self, group_name, user_id):
+ self.request_group_update(group_name)
+ self.groups[group_name].remove.remove(user_id)
+ self.send_update()
+
+ def send_update(self):
+ all_groups = self.groups.items()
+ res_group = [vars(i[1]) for i in all_groups] # Transform the Class into a dictionary
+
+ all_acls = self.acls.items()
+ res_acl = [vars(i[1]) for i in all_acls] # Transform the Class into a dictionary
+
+ cmd = messages.UpdateACL(channel_id=self.channel_id, inherit_acls=self.inherit_acls, chan_group=res_group, chan_acl=res_acl)
+ self.mumble_object.execute_command(cmd)
+
+
+class ChanGroup(dict):
+ """Object that stores and update all ChanGroups ACL"""
+
+ def __init__(self):
+ self.name = None
+ self.acl = None
+ self.inherited = None
+ self.inherit = None
+ self.inheritable = None
+ self.add = []
+ self.remove = []
+ self.inherited_members = []
+
+ def update(self, message):
+ """Update a ACL information, based on the incoming message"""
+ self.name = str(message.name)
+
+ if message.HasField('inherit'):
+ self.inherit = bool(message.inherit)
+ if message.HasField('inherited'):
+ self.inherited = bool(message.inherited)
+ if message.HasField('inheritable'):
+ self.inheritable = bool(message.inheritable)
+
+ if message.add:
+ for user in message.add:
+ self.add.append(int(user))
+ if message.remove:
+ for user in message.remove:
+ self.remove.append(int(user))
+ if message.inherited_members:
+ for user in message.inherited_members:
+ self.inherited_members.append(int(user))
+
+
+class ChanACL(dict):
+ """Object that stores and update all ChanACL ACL"""
+
+ def __init__(self):
+ self.apply_here = None
+ self.apply_subs = None
+ self.inherited = None
+ self.user_id = None
+ self.group = None
+ self.grant = None
+ self.deny = None
+
+ def update(self, message):
+ """Update a ACL information, based on the incoming message"""
+ if message.HasField('apply_here'):
+ self.apply_here = bool(message.apply_here)
+ if message.HasField('apply_subs'):
+ self.apply_subs = bool(message.apply_subs)
+ if message.HasField('inherited'):
+ self.inherited = bool(message.inherited)
+ if message.HasField('user_id'):
+ self.user_id = int(message.user_id)
+ if message.HasField('group'):
+ self.group = str(message.group)
+ if message.HasField('grant'):
+ self.grant = int(message.grant)
+ if message.HasField('deny'):
+ self.deny = int(message.deny)
diff --git a/pymumble_py3/blobs.py b/pymumble_py3/blobs.py
new file mode 100644
index 0000000..f32db7f
--- /dev/null
+++ b/pymumble_py3/blobs.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+import struct
+
+from .constants import *
+from .mumble_pb2 import RequestBlob
+
+
+class Blobs(dict):
+ """
+ Manage the Blob library
+ """
+ def __init__(self, mumble_object):
+ self.mumble_object = mumble_object
+
+ def get_user_comment(self, hash):
+ """Request the comment of a user"""
+ if hash in self:
+ return
+ request = RequestBlob()
+ request.session_comment.extend(struct.unpack("!5I", hash))
+
+ self.mumble_object.send_message(PYMUMBLE_MSG_TYPES_REQUESTBLOB, request)
+
+ def get_user_texture(self, hash):
+ """Request the image of a user"""
+ if hash in self:
+ return
+
+ request = RequestBlob()
+ request.session_texture.extend(struct.unpack("!5I", hash))
+
+ self.mumble_object.send_message(PYMUMBLE_MSG_TYPES_REQUESTBLOB, request)
+
+ def get_channel_description(self, hash):
+ """Request the description/comment of a channel"""
+ if hash in self:
+ return
+
+ request = RequestBlob()
+ request.channel_description.extend(struct.unpack("!5I", hash))
+
+ self.mumble_object.send_message(PYMUMBLE_MSG_TYPES_REQUESTBLOB, request)
diff --git a/pymumble_py3/callbacks.py b/pymumble_py3/callbacks.py
new file mode 100644
index 0000000..b46e883
--- /dev/null
+++ b/pymumble_py3/callbacks.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+
+from .errors import UnknownCallbackError
+from .constants import *
+import threading
+
+
+class CallBacks(dict):
+ """
+ Define the callbacks that can be registered by the application.
+ Multiple functions can be assigned to a callback using "add_callback"
+
+ The call is done from within the pymumble loop thread, it's important to
+ keep processing short to avoid delays on audio transmission
+ """
+ def __init__(self):
+ self.update({
+ PYMUMBLE_CLBK_CONNECTED: None, # Connection succeeded
+ PYMUMBLE_CLBK_DISCONNECTED: None, # Connection as been dropped
+ PYMUMBLE_CLBK_CHANNELCREATED: None, # send the created channel object as parameter
+ PYMUMBLE_CLBK_CHANNELUPDATED: None, # send the updated channel object and a dict with all the modified fields as parameter
+ PYMUMBLE_CLBK_CHANNELREMOVED: None, # send the removed channel object as parameter
+ PYMUMBLE_CLBK_USERCREATED: None, # send the added user object as parameter
+ PYMUMBLE_CLBK_USERUPDATED: None, # send the updated user object and a dict with all the modified fields as parameter
+ PYMUMBLE_CLBK_USERREMOVED: None, # send the removed user object and the mumble message as parameter
+ PYMUMBLE_CLBK_SOUNDRECEIVED: None, # send the user object that received the sound and the SoundChunk object itself
+ PYMUMBLE_CLBK_TEXTMESSAGERECEIVED: None, # Send the received message
+ PYMUMBLE_CLBK_CONTEXTACTIONRECEIVED: None, # Send the contextaction message
+ PYMUMBLE_CLBK_ACLRECEIVED: None, # Send the received ACL permissions object
+ PYMUMBLE_CLBK_PERMISSIONDENIED: None, # Permission Denied for some action, send information
+ })
+
+ def set_callback(self, callback, dest):
+ """Define the function to call for a specific callback. Suppress any existing callback function"""
+ if callback not in self:
+ raise UnknownCallbackError("Callback \"%s\" does not exists." % callback)
+
+ self[callback] = [dest]
+
+ def add_callback(self, callback, dest):
+ """Add the function to call for a specific callback."""
+ if callback not in self:
+ raise UnknownCallbackError("Callback \"%s\" does not exists." % callback)
+
+ if self[callback] is None:
+ self[callback] = list()
+ self[callback].append(dest)
+
+ def get_callback(self, callback):
+ """Get the functions assigned to a callback as a list. Return None if no callback defined"""
+ if callback not in self:
+ raise UnknownCallbackError("Callback \"%s\" does not exists." % callback)
+
+ return self[callback]
+
+ def remove_callback(self, callback, dest):
+ """Remove a specific function from a specific callback. Function object must be the one added before."""
+ if callback not in self:
+ raise UnknownCallbackError("Callback \"%s\" does not exists." % callback)
+
+ if self[callback] is None or dest not in self[callback]:
+ raise UnknownCallbackError("Function not registered for callback \"%s\"." % callback)
+
+ self[callback].remove(dest)
+ if len(self[callback]) == 0:
+ self[callback] = None
+
+ def reset_callback(self, callback):
+ """remove functions for a defined callback"""
+ if callback not in self:
+ raise UnknownCallbackError("Callback \"%s\" does not exists." % callback)
+
+ self[callback] = None
+
+ def call_callback(self, callback, *pos_parameters):
+ """Call all the registered function for a specific callback."""
+ if callback not in self:
+ raise UnknownCallbackError("Callback \"%s\" does not exists." % callback)
+
+ if self[callback]:
+ for func in self[callback]:
+ if callback is PYMUMBLE_CLBK_TEXTMESSAGERECEIVED:
+ thr = threading.Thread(target=func, args=pos_parameters)
+ thr.start()
+ else:
+ func(*pos_parameters)
+
+ def __call__(self, callback, *pos_parameters):
+ """shortcut to be able to call the dict element as a function"""
+ self.call_callback(callback, *pos_parameters)
+
+ def get_callbacks_list(self):
+ """Get a list of all callbacks"""
+ return list(self.keys())
diff --git a/pymumble_py3/channels.py b/pymumble_py3/channels.py
new file mode 100644
index 0000000..a3a2bc3
--- /dev/null
+++ b/pymumble_py3/channels.py
@@ -0,0 +1,274 @@
+# -*- coding: utf-8 -*-
+from .constants import *
+from threading import Lock
+from .errors import UnknownChannelError, TextTooLongError, ImageTooBigError
+from .acl import ACL
+from . import messages
+
+
+class Channels(dict):
+ """
+ Object that Stores all channels and their properties.
+ """
+
+ def __init__(self, mumble_object, callbacks):
+ self.mumble_object = mumble_object
+ self.callbacks = callbacks
+
+ self.lock = Lock()
+
+ def update(self, message):
+ """Update the channel information based on an incoming message"""
+ self.lock.acquire()
+
+ if message.channel_id not in self: # create the channel
+ self[message.channel_id] = Channel(self.mumble_object, message)
+ self.callbacks(PYMUMBLE_CLBK_CHANNELCREATED, self[message.channel_id])
+ else: # update the channel
+ actions = self[message.channel_id].update(message)
+ self.callbacks(PYMUMBLE_CLBK_CHANNELUPDATED, self[message.channel_id], actions)
+
+ self.lock.release()
+
+ def remove(self, id):
+ """Delete a channel when server signal the channel is removed"""
+ self.lock.acquire()
+
+ if id in self:
+ channel = self[id]
+ del self[id]
+ self.callbacks(PYMUMBLE_CLBK_CHANNELREMOVED, channel)
+
+ self.lock.release()
+
+ def find_by_tree(self, tree):
+ """Find a channel by its full path (a list with an element for each leaf)"""
+ if not getattr(tree, '__iter__', False):
+ tree = tree # function use argument as a list
+
+ current = self[0]
+
+ for name in tree: # going up the tree
+ found = False
+ for subchannel in self.get_childs(current):
+ if subchannel["name"] == name:
+ current = subchannel
+ found = True
+ break
+
+ if not found: # channel not found
+ err = "Cannot find channel %s" % str(tree)
+ raise UnknownChannelError(err)
+
+ return current
+
+ def get_childs(self, channel):
+ """Get the child channels of a channel in a list"""
+ childs = list()
+
+ for item in self.values():
+ if item.get('parent') is not None and item["parent"] == channel["channel_id"]:
+ childs.append(item)
+
+ return childs
+
+ def get_descendants(self, channel):
+ """Get all the descendant of a channel, in nested lists"""
+ descendants = list()
+
+ for subchannel in self.get_childs(channel):
+ descendants.append(self.get_childs(subchannel))
+
+ return descendants
+
+ def get_tree(self, channel):
+ """Get the whole list of channels, in a multidimensional list"""
+ tree = list()
+
+ current = channel
+
+ while current["channel_id"] != 0:
+ tree.insert(0, current)
+ current = self[current["channel_id"]]
+
+ tree.insert(0, self[0])
+
+ return tree
+
+ def find_by_name(self, name):
+ """Find a channel by name. Stop on the first that match"""
+ if name == "":
+ return self[0]
+
+ for obj in list(self.values()):
+ if obj["name"] == name:
+ return obj
+
+ err = "Channel %s does not exists" % name
+ raise UnknownChannelError(err)
+
+ def new_channel(self, parent_id, name, temporary=False):
+ cmd = messages.CreateChannel(parent_id, name, temporary)
+ self.mumble_object.execute_command(cmd)
+
+ def remove_channel(self, channel_id):
+ cmd = messages.RemoveChannel(channel_id)
+ self.mumble_object.execute_command(cmd)
+
+ def unlink_every_channel(self):
+ """
+ Unlink every channels in server.
+ So there will be no channel linked to other channel.
+ """
+ for channel in list(self.values()):
+ if "links" in channel:
+ cmd = messages.UnlinkChannel({"channel_id": channel['channel_id'], "remove_ids": channel["links"]})
+ self.mumble_object.execute_command(cmd)
+
+
+class Channel(dict):
+ """
+ Stores information about one specific channel
+ """
+
+ def __init__(self, mumble_object, message):
+ self.mumble_object = mumble_object
+ self["channel_id"] = message.channel_id
+ self.acl = ACL(mumble_object=mumble_object, channel_id=self["channel_id"])
+ self.update(message)
+
+ def get_users(self):
+ users = []
+ for user in list(self.mumble_object.users.values()):
+ if user["channel_id"] == self["channel_id"]:
+ users.append(user)
+ return users
+
+ def update(self, message):
+ """Update a channel based on an incoming message"""
+ actions = dict()
+
+ for (field, value) in message.ListFields():
+ if field.name in ("session", "actor", "description_hash"):
+ continue
+ actions.update(self.update_field(field.name, value))
+
+ if message.HasField("description_hash"):
+ actions.update(self.update_field("description_hash", message.description_hash))
+ if message.HasField("description"):
+ self.mumble_object.blobs[message.description_hash] = message.description
+ else:
+ self.mumble_object.blobs.get_channel_description(message.description_hash)
+
+ return actions # return a dict with updates performed, useful for the callback functions
+
+ def update_acl(self, message):
+ self.acl.update(message)
+
+ def get_id(self):
+ return self["channel_id"]
+
+ def update_field(self, name, field):
+ """Update one value"""
+ actions = dict()
+ if name not in self or self[name] != field:
+ self[name] = field
+ actions[name] = field
+
+ return actions # return a dict with updates performed, useful for the callback functions
+
+ def get_property(self, property):
+ if property in self:
+ return self[property]
+ else:
+ return None
+
+ def move_in(self, session=None):
+ """Ask to move a session in a specific channel. By default move pymumble own session"""
+ if session is None:
+ session = self.mumble_object.users.myself_session
+
+ cmd = messages.MoveCmd(session, self["channel_id"])
+ self.mumble_object.execute_command(cmd)
+
+ def remove(self):
+ cmd = messages.RemoveChannel(self["channel_id"])
+ self.mumble_object.execute_command(cmd)
+
+ def send_text_message(self, message):
+ """Send a text message to the channel."""
+
+ # TODO: This check should be done inside execute_command()
+ # However, this is currently not possible because execute_command() does
+ # not actually execute the command.
+ if len(message) > self.mumble_object.get_max_image_length() != 0:
+ raise ImageTooBigError(self.mumble_object.get_max_image_length())
+
+ if not (" self.mumble_object.get_max_message_length() != 0:
+ raise TextTooLongError(self.mumble_object.get_max_message_length())
+
+ session = self.mumble_object.users.myself_session
+
+ cmd = messages.TextMessage(session, self["channel_id"], message)
+ self.mumble_object.execute_command(cmd)
+
+ def link(self, channel_id):
+ """Link selected channel with other channel"""
+ cmd = messages.LinkChannel({"channel_id": self["channel_id"], "add_id": channel_id})
+ self.mumble_object.execute_command(cmd)
+
+ def unlink(self, channel_id):
+ """Unlink one channel which is linked to a specific channel."""
+ cmd = messages.UnlinkChannel({"channel_id": self["channel_id"], "remove_ids": [channel_id]})
+ self.mumble_object.execute_command(cmd)
+
+ def unlink_all(self):
+ """Unlink all channels which is linked to a specific channel."""
+ if "links" in self:
+ cmd = messages.UnlinkChannel({"channel_id": self["channel_id"], "remove_ids": self["links"]})
+ self.mumble_object.execute_command(cmd)
+
+ def rename_channel(self, name):
+ params = {
+ 'channel_id': self['channel_id'],
+ 'name': name
+ }
+ cmd = messages.UpdateChannel(params)
+ self.mumble_object.execute_command(cmd)
+
+ def move_channel(self, new_parent_id):
+ params = {
+ 'channel_id': self['channel_id'],
+ 'parent': new_parent_id
+ }
+ cmd = messages.UpdateChannel(params)
+ self.mumble_object.execute_command(cmd)
+
+ def set_channel_position(self, position):
+ params = {
+ 'channel_id': self['channel_id'],
+ 'position': position
+ }
+ cmd = messages.UpdateChannel(params)
+ self.mumble_object.execute_command(cmd)
+
+ def set_channel_max_users(self, max_users):
+ params = {
+ 'channel_id': self['channel_id'],
+ 'max_users': max_users
+ }
+ cmd = messages.UpdateChannel(params)
+ self.mumble_object.execute_command(cmd)
+
+ def set_channel_description(self, description):
+ params = {
+ 'channel_id': self['channel_id'],
+ 'description': description
+ }
+ cmd = messages.UpdateChannel(params)
+ self.mumble_object.execute_command(cmd)
+
+ def request_acl(self):
+ cmd = messages.QueryACL(self["channel_id"])
+ self.mumble_object.execute_command(cmd)
diff --git a/pymumble_py3/commands.py b/pymumble_py3/commands.py
new file mode 100644
index 0000000..51fe531
--- /dev/null
+++ b/pymumble_py3/commands.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+from threading import Lock
+from collections import deque
+
+
+class Commands:
+ """
+ Store to commands to be sent to the murmur server,
+ from whatever tread.
+ Each command has it's own lock semaphore to signal is received an answer
+ """
+ def __init__(self):
+ self.id = 0
+
+ self.queue = deque()
+
+ self.lock = Lock()
+
+ def new_cmd(self, cmd):
+ """Add a command to the queue"""
+ self.lock.acquire()
+
+ self.id += 1
+ cmd.cmd_id = self.id
+ self.queue.appendleft(cmd)
+ cmd.lock.acquire()
+
+ self.lock.release()
+ return cmd.lock
+
+ def is_cmd(self):
+ """Check if there is a command waiting in the queue"""
+ if len(self.queue) > 0:
+ return True
+ else:
+ return False
+
+ def pop_cmd(self):
+ """Return the next command and remove it from the queue"""
+ self.lock.acquire()
+
+ if len(self.queue) > 0:
+ question = self.queue.pop()
+ self.lock.release()
+ return question
+ else:
+ self.lock.release()
+ return None
+
+ def answer(self, cmd):
+ """Unlock the command to signal it's completion"""
+ cmd.lock.release()
+
+
diff --git a/pymumble_py3/constants.py b/pymumble_py3/constants.py
new file mode 100644
index 0000000..5cbb4c7
--- /dev/null
+++ b/pymumble_py3/constants.py
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+
+import platform
+import sys
+
+PYMUMBLE_VERSION = "1.7"
+
+# ============================================================================
+# Tunable parameters
+# ============================================================================
+PYMUMBLE_CONNECTION_RETRY_INTERVAL = 10 # in sec
+PYMUMBLE_AUDIO_PER_PACKET = float(20)/1000 # size of one audio packet in sec
+PYMUMBLE_BANDWIDTH = 50 * 1000 # total outgoing bitrate in bit/seconds
+PYMUMBLE_LOOP_RATE = 0.01 # pause done between two iteration of the main loop of the mumble thread, in sec
+ # should be small enough to manage the audio output, so smaller than PYMUMBLE_AUDIO_PER_PACKET
+
+# ============================================================================
+# Constants
+# ============================================================================
+PYMUMBLE_PROTOCOL_VERSION = (1, 4, 287)
+PYMUMBLE_VERSION_STRING = "PyMumble %s" % PYMUMBLE_VERSION
+PYMUMBLE_OS_STRING = "PyMumble %s" % PYMUMBLE_VERSION
+PYMUMBLE_OS_VERSION_STRING = "Python %s - %s %s" % (sys.version, platform.system(), platform.release())
+
+PYMUMBLE_PING_DELAY = 10 # interval between 2 pings in sec
+
+PYMUMBLE_SAMPLERATE = 48000 # in hz
+
+PYMUMBLE_SEQUENCE_DURATION = float(10)/1000 # in sec
+PYMUMBLE_SEQUENCE_RESET_INTERVAL = 5 # in sec
+PYMUMBLE_READ_BUFFER_SIZE = 4096 # how much bytes to read at a time from the control socket, in bytes
+
+# client connection state
+PYMUMBLE_CONN_STATE_NOT_CONNECTED = 0
+PYMUMBLE_CONN_STATE_AUTHENTICATING = 1
+PYMUMBLE_CONN_STATE_CONNECTED = 2
+PYMUMBLE_CONN_STATE_FAILED = 3
+
+# Mumble control messages types
+PYMUMBLE_MSG_TYPES_VERSION = 0
+PYMUMBLE_MSG_TYPES_UDPTUNNEL = 1
+PYMUMBLE_MSG_TYPES_AUTHENTICATE = 2
+PYMUMBLE_MSG_TYPES_PING = 3
+PYMUMBLE_MSG_TYPES_REJECT = 4
+PYMUMBLE_MSG_TYPES_SERVERSYNC = 5
+PYMUMBLE_MSG_TYPES_CHANNELREMOVE = 6
+PYMUMBLE_MSG_TYPES_CHANNELSTATE = 7
+PYMUMBLE_MSG_TYPES_USERREMOVE = 8
+PYMUMBLE_MSG_TYPES_USERSTATE = 9
+PYMUMBLE_MSG_TYPES_BANLIST = 10
+PYMUMBLE_MSG_TYPES_TEXTMESSAGE = 11
+PYMUMBLE_MSG_TYPES_PERMISSIONDENIED = 12
+PYMUMBLE_MSG_TYPES_ACL = 13
+PYMUMBLE_MSG_TYPES_QUERYUSERS = 14
+PYMUMBLE_MSG_TYPES_CRYPTSETUP = 15
+PYMUMBLE_MSG_TYPES_CONTEXTACTIONMODIFY = 16
+PYMUMBLE_MSG_TYPES_CONTEXTACTION = 17
+PYMUMBLE_MSG_TYPES_USERLIST = 18
+PYMUMBLE_MSG_TYPES_VOICETARGET = 19
+PYMUMBLE_MSG_TYPES_PERMISSIONQUERY = 20
+PYMUMBLE_MSG_TYPES_CODECVERSION = 21
+PYMUMBLE_MSG_TYPES_USERSTATS = 22
+PYMUMBLE_MSG_TYPES_REQUESTBLOB = 23
+PYMUMBLE_MSG_TYPES_SERVERCONFIG = 24
+
+# callbacks names
+PYMUMBLE_CLBK_CONNECTED = "connected"
+PYMUMBLE_CLBK_DISCONNECTED = "disconnected"
+PYMUMBLE_CLBK_CHANNELCREATED = "channel_created"
+PYMUMBLE_CLBK_CHANNELUPDATED = "channel_updated"
+PYMUMBLE_CLBK_CHANNELREMOVED = "channel_remove"
+PYMUMBLE_CLBK_USERCREATED = "user_created"
+PYMUMBLE_CLBK_USERUPDATED = "user_updated"
+PYMUMBLE_CLBK_USERREMOVED = "user_removed"
+PYMUMBLE_CLBK_SOUNDRECEIVED = "sound_received"
+PYMUMBLE_CLBK_TEXTMESSAGERECEIVED = "text_received"
+PYMUMBLE_CLBK_CONTEXTACTIONRECEIVED = "contextAction_received"
+PYMUMBLE_CLBK_ACLRECEIVED = "acl_received"
+PYMUMBLE_CLBK_PERMISSIONDENIED = "permission_denied"
+
+# audio types
+PYMUMBLE_AUDIO_TYPE_CELT_ALPHA = 0
+PYMUMBLE_AUDIO_TYPE_PING = 1
+PYMUMBLE_AUDIO_TYPE_SPEEX = 2
+PYMUMBLE_AUDIO_TYPE_CELT_BETA = 3
+PYMUMBLE_AUDIO_TYPE_OPUS = 4
+PYMUMBLE_AUDIO_TYPE_OPUS_PROFILE = "audio" # "voip"
+
+# command names
+PYMUMBLE_CMD_MOVE = "move"
+PYMUMBLE_CMD_MODUSERSTATE = "update_user"
+PYMUMBLE_CMD_TEXTMESSAGE = "text_message"
+PYMUMBLE_CMD_TEXTPRIVATEMESSAGE = "text_private_message"
+PYMUMBLE_CMD_LINKCHANNEL = "link"
+PYMUMBLE_CMD_UNLINKCHANNEL = "unlink"
+PYMUMBLE_CMD_QUERYACL = "get_acl"
+PYMUMBLE_CMD_UPDATEACL = "update_acl"
+PYMUMBLE_CMD_REMOVEUSER = "remove_user"
+PYMUMBLE_CMD_UPDATECHANNEL = "update_channel"
diff --git a/pymumble_py3/crypto.py b/pymumble_py3/crypto.py
new file mode 100644
index 0000000..63a6b2a
--- /dev/null
+++ b/pymumble_py3/crypto.py
@@ -0,0 +1,383 @@
+'''
+OCB2 crypto, broadly following the implementation from Mumble
+'''
+from typing import Tuple
+import struct
+import time
+from math import ceil
+
+from Crypto.Cipher import AES
+from Crypto.Random import get_random_bytes
+
+
+AES_BLOCK_SIZE = 128 // 8 # Number of bytes in a block
+AES_KEY_SIZE_BITS = 128
+AES_KEY_SIZE_BYTES = AES_KEY_SIZE_BITS // 8
+SHIFTBITS = 63 # Shift size for S2 operation
+MAX64 = (1 << 64) - 1 # Maximum value of uint64
+
+
+class EncryptFailedException(Exception):
+ pass
+
+
+class DecryptFailedException(Exception):
+ pass
+
+
+class CryptStateOCB2:
+ """
+ State tracker for AES-OCB2 crypto.
+ All encryption/decryption should be done through this class
+ and not the `ocb_*` functions.
+
+ A random key and IVs are chosen upon initialization; these can be
+ replaced using `set_key`.
+
+ Attributes intended for external access:
+ raw_key
+ encrypt_iv
+ decrypt_iv
+ decrypt_history
+
+ uiGood
+ uiLate
+ uiLost
+ tLastGood
+ """
+ _raw_key: bytes # AES key; access through `raw_key` property
+ _aes: object # pycrypto AES cipher object, replaced when `raw_key` is changed
+ _encrypt_iv: bytearray # IV for encryption, access through `encrypt_iv` property
+ _decrypt_iv: bytearray # IV for decryption, access through `decrypt_iv` property
+ decrypt_history: bytearray # History of previous decrypt_iv values
+
+ # Statistics:
+ uiGood: int # Number of packets successfully decrypted
+ uiLate: int # Number of packets which arrived out of order
+ uiLost: int # Number of packets which did not arrive in order (may arrive late)
+ tLastGood: float # time.perf_counter() value for latest good packet
+
+ def __init__(self):
+ self.uiGood = 0
+ self.uiLate = 0
+ self.uiLost = 0
+ self.tLastGood = 0
+
+ self._raw_key = get_random_bytes(AES_KEY_SIZE_BYTES)
+ self._encrypt_iv = get_random_bytes(AES_BLOCK_SIZE)
+ self._decrypt_iv = get_random_bytes(AES_BLOCK_SIZE)
+ self._aes = None
+ self.decrypt_history = bytearray(0x100)
+
+ @property
+ def raw_key(self) -> bytes:
+ return self._raw_key
+
+ @raw_key.setter
+ def raw_key(self, rkey: bytes):
+ if len(rkey) != AES_KEY_SIZE_BYTES:
+ raise Exception('raw_key has wrong length')
+ self._raw_key = bytes(rkey)
+ self._aes = AES.new(key=self.raw_key, mode=AES.MODE_ECB)
+
+ @property
+ def encrypt_iv(self) -> bytearray:
+ return self._encrypt_iv
+
+ @encrypt_iv.setter
+ def encrypt_iv(self, eiv: bytearray):
+ if len(eiv) != AES_BLOCK_SIZE:
+ raise Exception('encrypt_iv wrong length')
+ self._encrypt_iv = bytearray(eiv)
+
+ @property
+ def decrypt_iv(self) -> bytearray:
+ return self._decrypt_iv
+
+ @decrypt_iv.setter
+ def decrypt_iv(self, div: bytearray):
+ if len(div) != AES_BLOCK_SIZE:
+ raise Exception('decrypt_iv has wrong length')
+ self._decrypt_iv = bytearray(div)
+
+ def gen_key(self):
+ """
+ Randomly generate new keys
+ """
+ self.raw_key = get_random_bytes(AES_KEY_SIZE_BYTES)
+ self.encrypt_iv = get_random_bytes(AES_BLOCK_SIZE)
+ self.decrypt_iv = get_random_bytes(AES_BLOCK_SIZE)
+
+ def set_key(self, raw_key: bytes, encrypt_iv: bytearray, decrypt_iv: bytearray):
+ """
+ Set new keys
+
+ Args:
+ raw_key: AES key
+ encrypt_iv: IV for encryption
+ decrypt_iv: IV for decrpytion
+ """
+ self.raw_key = raw_key
+ self.encrypt_iv = encrypt_iv
+ self.decrypt_iv = decrypt_iv
+
+ def encrypt(self, source: bytes) -> bytes:
+ """
+ Encrypt a message
+
+ Args:
+ source: The plaintext bytes to be encrypted
+
+ Returns:
+ Encrypted (ciphertext) bytes
+
+ Raises:
+ EncryptFailedException if `source` would result in a vulnerable packet
+ """
+ eiv = increment_iv(self.encrypt_iv)
+ self.encrypt_iv = eiv
+
+ dst, tag = ocb_encrypt(self._aes, source, bytes(eiv))
+
+ head = bytes((eiv[0], *tag[:3]))
+ return head + dst
+
+ def decrypt(self, source: bytes, len_plain: int) -> bytes:
+ """
+ Decrypt a message
+
+ Args:
+ source: The ciphertext bytes to be decrypted
+ len_plain: The length of the plaintext
+
+ Returns:
+ Decrypted (plaintext) bytes
+
+ Raises:
+ DecryptFailedException:
+ - if `source` is too short
+ - packet is out of order or duplicate
+ - packet was could have been tampered with
+ """
+ if len(source) < 4:
+ raise DecryptFailedException('Source <4 bytes long!')
+
+ div = self.decrypt_iv.copy()
+ ivbyte = source[0]
+ late = False
+ lost = 0
+
+ if (div[0] + 1) & 0xFF == ivbyte:
+ # In order as expected.
+ if ivbyte > div[0]:
+ div[0] = ivbyte
+ elif ivbyte < div[0]:
+ div[0] = ivbyte
+ div = increment_iv(div, 1)
+ else:
+ raise DecryptFailedException('ivbyte == decrypt_iv[0]')
+ else:
+ # This is either out of order or a repeat.
+ diff = ivbyte - div[0]
+ if diff > 128:
+ diff -= 256
+ elif diff < -128:
+ diff += 256
+
+ if ivbyte < div[0] and -30 < diff < 0:
+ # Late packet, but no wraparound.
+ late = True
+ lost = -1
+ div[0] = ivbyte
+ elif ivbyte > div[0] and -30 < diff < 0:
+ # Last was 0x02, here comes 0xff from last round
+ late = True
+ lost = -1
+ div[0] = ivbyte
+ div = decrement_iv(div, 1)
+ elif ivbyte > div[0] and diff > 0:
+ # Lost a few packets, but beyond that we're good.
+ lost = ivbyte - div[0] - 1
+ div[0] = ivbyte
+ elif ivbyte < div[0] and diff > 0:
+ # Lost a few packets, and wrapped around
+ lost = 0x100 - div[0] + ivbyte - 1
+ div[0] = ivbyte
+ div = increment_iv(div, 1)
+ else:
+ raise DecryptFailedException('Lost too many packets?')
+
+ if self.decrypt_history[div[0]] == div[1]:
+ raise DecryptFailedException('decrypt_iv in history')
+
+ dst, tag = ocb_decrypt(self._aes, source[4:], bytes(div), len_plain)
+
+ if tag[:3] != source[1:4]:
+ raise DecryptFailedException('Tag did not match!')
+
+ self.decrypt_history[div[0]] = div[1]
+
+ if not late:
+ self.decrypt_iv = div
+ else:
+ self.uiLate += 1
+
+ self.uiGood += 1
+ self.uiLost += lost
+
+ self.tLastGood = time.perf_counter()
+
+ return dst
+
+
+def ocb_encrypt(aes: object,
+ plain: bytes,
+ nonce: bytes,
+ *,
+ insecure=False,
+ ) -> Tuple[bytes, bytes]:
+ """
+ Encrypt a message.
+ This should be called from CryptStateOCB2.encrypt() and not independently.
+
+ Args:
+ aes: AES-ECB cipher object
+ plain: The plaintext bytes to be encrypted
+ nonce: The encryption IV
+
+ Returns:
+ Encrypted (ciphertext) bytes and tag
+
+ Raises:
+ EncryptFailedException if `source` would result in a vulnerable packet
+ """
+ delta = aes.encrypt(nonce)
+ checksum = bytes(AES_BLOCK_SIZE)
+ plain_block = b''
+
+ pos = 0
+ encrypted = bytearray(ceil(len(plain) / AES_BLOCK_SIZE) * AES_BLOCK_SIZE)
+ while len(plain) - pos > AES_BLOCK_SIZE:
+ plain_block = plain[pos:pos + AES_BLOCK_SIZE]
+ delta = S2(delta)
+ encrypted_block = xor(delta, aes.encrypt(xor(delta, plain_block)))
+ checksum = xor(checksum, plain_block)
+
+ encrypted[pos:pos + AES_BLOCK_SIZE] = encrypted_block
+ pos += AES_BLOCK_SIZE
+
+ # Counter-cryptanalysis described in section 9 of https://eprint.iacr.org/2019/311
+ # For an attack, the second to last block (i.e. the last iteration of this loop)
+ # must be all 0 except for the last byte (which may be 0 - 128).
+ if not insecure and bytes(plain_block[:-1]) == bytes(AES_BLOCK_SIZE - 1):
+ raise EncryptFailedException('Insecure input block: ' +
+ 'see section 9 of https://eprint.iacr.org/2019/311')
+
+ len_remaining = len(plain) - pos
+ delta = S2(delta)
+ pad_in = struct.pack('>QQ', 0, len_remaining * 8)
+ pad = aes.encrypt(xor(pad_in, delta))
+ plain_block = plain[pos:] + pad[len_remaining - AES_BLOCK_SIZE:]
+
+ checksum = xor(checksum, plain_block)
+ encrypted_block = xor(pad, plain_block)
+ encrypted[pos:] = encrypted_block
+
+ delta = xor(delta, S2(delta))
+ tag = aes.encrypt(xor(delta, checksum))
+
+ return encrypted, tag
+
+
+def ocb_decrypt(aes: object,
+ encrypted: bytes,
+ nonce: bytes,
+ len_plain: int,
+ *,
+ insecure=False,
+ ) -> Tuple[bytes, bytes]:
+ """
+ Decrypt a message.
+ This should be called from CryptStateOCB2.decrypt() and not independently.
+
+ Args:
+ aes: AES-ECB cipher object
+ encrypted: The ciphertext bytes to be decrypted
+ nonce: The decryption IV
+ len_plain: The length of the desired plaintext
+
+ Returns:
+ Decrypted (plaintext) bytes and tag
+
+ Raises:
+ DecryptFailedException:
+ - if `source` is too short
+ - packet is out of order or duplicate
+ - packet was could have been tampered with
+ """
+ delta = aes.encrypt(nonce)
+ checksum = bytes(AES_BLOCK_SIZE)
+ plain = bytearray(len_plain)
+
+ pos = 0
+ while len_plain - pos > AES_BLOCK_SIZE:
+ encrypted_block = encrypted[pos:pos + AES_BLOCK_SIZE]
+ delta = S2(delta)
+ tmp = aes.decrypt(xor(delta, encrypted_block))
+ plain_block = xor(delta, tmp)
+ checksum = xor(checksum, plain_block)
+
+ plain[pos:pos + AES_BLOCK_SIZE] = plain_block
+ pos += AES_BLOCK_SIZE
+
+ len_remaining = len_plain - pos
+ delta = S2(delta)
+ pad_in = struct.pack('>QQ', 0, len_remaining * 8)
+ pad = aes.encrypt(xor(pad_in, delta))
+ encrypted_zeropad = encrypted[pos:] + bytes(AES_BLOCK_SIZE - len_remaining)
+ plain_block = xor(encrypted_zeropad, pad)
+
+ checksum = xor(checksum, plain_block)
+ plain[pos:] = plain_block[:len_remaining]
+
+ # Counter-cryptanalysis described in section 9 of https://eprint.iacr.org/2019/311
+ # In an attack, the decrypted last block would need to equal `delta ^ len(128)`.
+ # With a bit of luck (or many packets), smaller values than 128 (i.e. non-full blocks) are also
+ # feasible, so we check `plain_block` instead of `plain`.
+ # Since our `len` only ever modifies the last byte, we simply check all remaining ones.
+ if not insecure and plain_block[:-1] == delta[:-1]:
+ raise DecryptFailedException('Possibly tampered/able block, discarding.')
+
+ delta = xor(delta, S2(delta))
+ tag = aes.encrypt(xor(delta, checksum))
+ return plain, tag
+
+
+def increment_iv(iv: bytearray, start: int = 0) -> bytearray:
+ for i in range(start, AES_BLOCK_SIZE):
+ iv[i] = (iv[i] + 1) % 0x100
+ if iv[i] != 0:
+ break
+ return iv
+
+
+def decrement_iv(iv: bytearray, start: int = 0) -> bytearray:
+ for i in range(start, AES_BLOCK_SIZE):
+ iv[i] = (iv[i] - 1) % 0x100
+ if iv[i] != 0xFF:
+ break
+ return iv
+
+
+def xor(a: bytes, b: bytes) -> bytes:
+ return bytes(aa ^ bb for aa, bb in zip(a, b))
+
+
+def S2(block: bytes) -> bytes:
+ ll, uu = struct.unpack('>QQ', block)
+ carry = ll >> 63
+ block = struct.pack('>QQ',
+ ((ll << 1) | (uu >> 63)) & MAX64,
+ ((uu << 1) ^ (carry * 0x87)) & MAX64)
+ return block
+
diff --git a/pymumble_py3/errors.py b/pymumble_py3/errors.py
new file mode 100644
index 0000000..8d67f5e
--- /dev/null
+++ b/pymumble_py3/errors.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+
+
+class CodecNotSupportedError(Exception):
+ """Thrown when receiving an audio packet from an unsupported codec"""
+
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return repr(self.value)
+
+
+class ConnectionRejectedError(Exception):
+ """Thrown when server reject the connection"""
+
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return repr(self.value)
+
+
+class InvalidFormatError(Exception):
+ """Thrown when receiving a packet not understood"""
+
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return repr(self.value)
+
+
+class UnknownCallbackError(Exception):
+ """Thrown when asked for an unknown callback"""
+
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return repr(self.value)
+
+
+class UnknownChannelError(Exception):
+ """Thrown when using an unknown channel"""
+
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return repr(self.value)
+
+
+class InvalidSoundDataError(Exception):
+ """Thrown when trying to send an invalid audio pcm data"""
+
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return repr(self.value)
+
+
+class InvalidVarInt(Exception):
+ """Thrown when trying to decode an invalid varint"""
+
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return repr(self.value)
+
+
+class TextTooLongError(Exception):
+ """Thrown when trying to send a message which is longer than allowed"""
+
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return 'Maximum Text allowed length: {}'.format(self.value)
+
+
+class ImageTooBigError(Exception):
+ """Thrown when trying to send a message or image which is longer than allowed"""
+
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return 'Maximum Text/Image allowed length: {}'.format(self.value)
+
+
+class ACLChanGroupNotExist(Exception):
+ """Thrown when trying to update an non-existant ACL ChanGroup"""
+
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return 'ACL ChanGroup does not exist: {}'.format(self.value)
diff --git a/pymumble_py3/messages.py b/pymumble_py3/messages.py
new file mode 100644
index 0000000..6bdba92
--- /dev/null
+++ b/pymumble_py3/messages.py
@@ -0,0 +1,158 @@
+# -*- coding: utf-8 -*-
+from .constants import *
+from threading import Lock
+
+
+class Cmd:
+ """
+ Define a command object, used to ask an action from the pymumble thread,
+ usually to forward to the murmur server
+ """
+
+ def __init__(self):
+ self.cmd_id = None
+ self.lock = Lock()
+
+ self.cmd = None
+ self.parameters = None
+ self.response = None
+
+
+class MoveCmd(Cmd):
+ """Command to move a user from channel"""
+
+ def __init__(self, session, channel_id):
+ Cmd.__init__(self)
+
+ self.cmd = PYMUMBLE_CMD_MOVE
+ self.parameters = {"session": session,
+ "channel_id": channel_id}
+
+
+class TextMessage(Cmd):
+ """Command to send a text message"""
+
+ def __init__(self, session, channel_id, message):
+ Cmd.__init__(self)
+
+ self.cmd = PYMUMBLE_CMD_TEXTMESSAGE
+ self.parameters = {"session": session,
+ "channel_id": channel_id,
+ "message": message}
+
+
+class TextPrivateMessage(Cmd):
+ """Command to send a private text message"""
+
+ def __init__(self, session, message):
+ Cmd.__init__(self)
+
+ self.cmd = PYMUMBLE_CMD_TEXTPRIVATEMESSAGE
+ self.parameters = {"session": session,
+ "message": message}
+
+
+class ModUserState(Cmd):
+ """Command to change a user state"""
+
+ def __init__(self, session, params):
+ Cmd.__init__(self)
+
+ self.cmd = PYMUMBLE_CMD_MODUSERSTATE
+ self.parameters = params
+
+
+class RemoveUser(Cmd):
+ """Command to kick (ban=False) or ban (ban=True) a user"""
+
+ def __init__(self, session, params):
+ Cmd.__init__(self)
+
+ self.cmd = PYMUMBLE_CMD_REMOVEUSER
+ self.parameters = params
+
+
+class CreateChannel(Cmd):
+ """Command to create channel"""
+
+ def __init__(self, parent, name, temporary):
+ Cmd.__init__(self)
+
+ self.cmd = PYMUMBLE_MSG_TYPES_CHANNELSTATE
+ self.parameters = {"parent": parent,
+ "name": name,
+ "temporary": temporary}
+
+
+class RemoveChannel(Cmd):
+ """Command to create channel"""
+
+ def __init__(self, channel_id):
+ Cmd.__init__(self)
+
+ self.cmd = PYMUMBLE_MSG_TYPES_CHANNELREMOVE
+ self.parameters = {"channel_id": channel_id}
+
+
+class UpdateChannel(Cmd):
+ """Command to update channel"""
+
+ def __init__(self, params):
+ Cmd.__init__(self)
+
+ self.cmd = PYMUMBLE_CMD_UPDATECHANNEL
+ self.parameters = params
+
+
+class VoiceTarget(Cmd):
+ """Command to create a whisper"""
+
+ def __init__(self, voice_id, targets):
+ Cmd.__init__(self)
+
+ self.cmd = PYMUMBLE_MSG_TYPES_VOICETARGET
+ self.parameters = {"id": voice_id,
+ "targets": targets}
+
+
+class LinkChannel(Cmd):
+ """Command to link channel"""
+
+ def __init__(self, params):
+ Cmd.__init__(self)
+
+ self.cmd = PYMUMBLE_CMD_LINKCHANNEL
+ self.parameters = params
+
+
+class UnlinkChannel(Cmd):
+ """Command to unlink channel"""
+
+ def __init__(self, params):
+ Cmd.__init__(self)
+
+ self.cmd = PYMUMBLE_CMD_UNLINKCHANNEL
+ self.parameters = params
+
+
+class QueryACL(Cmd):
+ """Command to query ACL information for channel"""
+
+ def __init__(self, channel_id):
+ Cmd.__init__(self)
+
+ self.cmd = PYMUMBLE_CMD_QUERYACL
+ self.parameters = {"channel_id": channel_id}
+
+
+class UpdateACL(Cmd):
+ """Command to Update ACL information for channel"""
+
+ def __init__(self, channel_id, inherit_acls, chan_group, chan_acl):
+ Cmd.__init__(self)
+
+ self.cmd = PYMUMBLE_CMD_UPDATEACL
+ self.parameters = {"channel_id": channel_id,
+ "inherit_acls": inherit_acls,
+ "chan_group": chan_group,
+ "chan_acl": chan_acl}
diff --git a/pymumble_py3/mumble.py b/pymumble_py3/mumble.py
new file mode 100644
index 0000000..751a359
--- /dev/null
+++ b/pymumble_py3/mumble.py
@@ -0,0 +1,802 @@
+# -*- coding: utf-8 -*-
+import threading
+import logging
+import time
+import select
+import socket
+import ssl
+import struct
+
+from .errors import *
+from .constants import *
+from . import users
+from . import channels
+from . import blobs
+from . import commands
+from . import callbacks
+from . import tools
+
+from . import mumble_pb2
+
+
+def _wrap_socket(sock, keyfile=None, certfile=None, verify_mode=ssl.CERT_NONE, server_hostname=None):
+ try:
+ ssl_context = ssl.create_default_context()
+ if certfile:
+ ssl_context.load_cert_chain(certfile, keyfile)
+ ssl_context.check_hostname = (verify_mode != ssl.CERT_NONE) and (server_hostname is not None)
+ ssl_context.verify_mode = verify_mode
+ return ssl_context.wrap_socket(sock, server_hostname=server_hostname)
+ except AttributeError:
+ try:
+ return ssl.wrap_socket(sock, keyfile, certfile, cert_reqs=verify_mode, ssl_version=ssl.PROTOCOL_TLS)
+ except AttributeError:
+ return ssl.wrap_socket(sock, keyfile, certfile, cert_reqs=verify_mode, ssl_version=ssl.PROTOCOL_TLSv1)
+
+
+class Mumble(threading.Thread):
+ """
+ Mumble client library main object.
+ basically a thread
+ """
+
+ def __init__(self, host, user, port=64738, password='', certfile=None, keyfile=None, reconnect=False, tokens=None, stereo=False, debug=False, client_type=0):
+ """
+ host=mumble server hostname or address
+ port=mumble server port
+ user=user to use for the connection
+ password=password for the connection
+ certfile=client certificate to authenticate the connection
+ keyfile=private key associated with the client certificate
+ reconnect=if True, try to reconnect if disconnected
+ tokens=channel access tokens as a list of strings
+ stereo=enable stereo transmission
+ debug=if True, send debugging messages (lot of...) to the stdout
+ client_type=if 1, flag connection as bot
+ """
+ # TODO: use UDP audio
+ threading.Thread.__init__(self)
+
+ if tokens is None:
+ tokens = []
+ self.Log = logging.getLogger("PyMumble") # logging object for errors and debugging
+ if debug:
+ self.Log.setLevel(logging.DEBUG)
+ else:
+ self.Log.setLevel(logging.ERROR)
+
+ ch = logging.StreamHandler()
+ ch.setLevel(logging.DEBUG)
+ formatter = logging.Formatter('%(asctime)s-%(name)s-%(levelname)s-%(message)s')
+ ch.setFormatter(formatter)
+ self.Log.addHandler(ch)
+
+ self.parent_thread = threading.current_thread() # main thread of the calling application
+ self.mumble_thread = None # thread of the mumble client library
+
+ self.host = host
+ self.port = port
+ self.user = user
+ self.password = password
+ self.certfile = certfile
+ self.keyfile = keyfile
+ self.reconnect = reconnect
+ self.tokens = tokens
+ self.__opus_profile = PYMUMBLE_AUDIO_TYPE_OPUS_PROFILE
+ self.stereo = stereo
+ self.client_type = client_type
+
+ if stereo:
+ self.Log.debug("Working in STEREO mode.")
+ else:
+ self.Log.debug("Working in MONO mode.")
+
+ self.receive_sound = False # set to True to treat incoming audio, otherwise it is simply ignored
+
+ self.loop_rate = PYMUMBLE_LOOP_RATE
+
+ self.application = PYMUMBLE_VERSION_STRING
+
+ self.callbacks = callbacks.CallBacks() # callbacks management
+
+ self.ready_lock = threading.Lock() # released when the connection is fully established with the server
+ self.ready_lock.acquire()
+
+ self.positional = None
+
+ def init_connection(self):
+ """Initialize variables that are local to a connection, (needed if the client automatically reconnect)"""
+ self.ready_lock.acquire(False) # reacquire the ready-lock in case of reconnection
+
+ self.connected = PYMUMBLE_CONN_STATE_NOT_CONNECTED
+ self.control_socket = None
+ self.media_socket = None # Not implemented - for UDP media
+
+ self.bandwidth = PYMUMBLE_BANDWIDTH # reset the outgoing bandwidth to it's default before connecting
+ self.server_max_bandwidth = None
+ self.udp_active = False
+
+ # defaults according to https://wiki.mumble.info/wiki/Murmur.ini
+ self.server_allow_html = True
+ self.server_max_message_length = 5000
+ self.server_max_image_message_length = 131072
+
+ self.users = users.Users(self, self.callbacks) # contains the server's connected users information
+ self.channels = channels.Channels(self, self.callbacks) # contains the server's channels information
+ self.blobs = blobs.Blobs(self) # manage the blob objects
+ if self.receive_sound:
+ from . import soundoutput
+ self.sound_output = soundoutput.SoundOutput(self, PYMUMBLE_AUDIO_PER_PACKET, self.bandwidth, stereo=self.stereo, opus_profile=self.__opus_profile) # manage the outgoing sounds
+ else:
+ self.sound_output = None
+ self.commands = commands.Commands() # manage commands sent between the main and the mumble threads
+
+ self.receive_buffer = bytes() # initialize the control connection input buffer
+ self.ping_stats = {"last_rcv": 0, "time_send": 0, "nb": 0, "avg": 40.0, "var": 0.0} # Set / reset ping stats
+
+ def run(self):
+ """Connect to the server and start the loop in its thread. Retry if requested"""
+ self.mumble_thread = threading.current_thread()
+
+ # loop if auto-reconnect is requested
+ while True:
+ self.init_connection() # reset the connection-specific object members
+
+ if self.connect() >= PYMUMBLE_CONN_STATE_FAILED: # some error occurred, exit here
+ self.ready_lock.release()
+ if not self.reconnect or not self.parent_thread.is_alive():
+ raise ConnectionRejectedError("Connection error with the Mumble (murmur) Server")
+ else:
+ time.sleep(PYMUMBLE_CONNECTION_RETRY_INTERVAL)
+ continue
+
+ try:
+ self.loop()
+ except socket.error:
+ self.connected = PYMUMBLE_CONN_STATE_NOT_CONNECTED
+
+ if not self.reconnect or not self.parent_thread.is_alive():
+ self.callbacks(PYMUMBLE_CLBK_DISCONNECTED)
+ break
+
+ self.callbacks(PYMUMBLE_CLBK_DISCONNECTED)
+ time.sleep(PYMUMBLE_CONNECTION_RETRY_INTERVAL)
+
+ def connect(self):
+ """Connect to the server"""
+ try:
+ # Get IPv4/IPv6 server address
+ server_info = socket.getaddrinfo(self.host, self.port, type=socket.SOCK_STREAM)
+
+ # Connect the SSL tunnel
+ self.Log.debug("connecting to %s (%s) on port %i.", self.host, server_info[0][1], self.port)
+ std_sock = socket.socket(server_info[0][0], socket.SOCK_STREAM)
+ std_sock.settimeout(10)
+ except socket.error:
+ self.connected = PYMUMBLE_CONN_STATE_FAILED
+ return self.connected
+
+ # FIXME: Default verify_mode and server_hostname are not safe, as no
+ # certificate checks are performed.
+ self.control_socket = _wrap_socket(std_sock, self.keyfile, self.certfile)
+ try:
+ self.control_socket.connect((self.host, self.port))
+ self.control_socket.setblocking(False)
+
+ # Perform the Mumble authentication
+ version = mumble_pb2.Version()
+ if PYMUMBLE_PROTOCOL_VERSION[2] > 255:
+ version.version_v1 = (PYMUMBLE_PROTOCOL_VERSION[0] << 16) + (PYMUMBLE_PROTOCOL_VERSION[1] << 8) + 255
+ else:
+ version.version_v1 = (PYMUMBLE_PROTOCOL_VERSION[0] << 16) + (PYMUMBLE_PROTOCOL_VERSION[1] << 8) + (PYMUMBLE_PROTOCOL_VERSION[2])
+ version.version_v2 = (PYMUMBLE_PROTOCOL_VERSION[0] << 48) + (PYMUMBLE_PROTOCOL_VERSION[1] << 32) + (PYMUMBLE_PROTOCOL_VERSION[2] << 16)
+ version.release = self.application
+ version.os = PYMUMBLE_OS_STRING
+ version.os_version = PYMUMBLE_OS_VERSION_STRING
+ self.Log.debug("sending: version: %s", version)
+ self.send_message(PYMUMBLE_MSG_TYPES_VERSION, version)
+
+ authenticate = mumble_pb2.Authenticate()
+ authenticate.username = self.user
+ authenticate.password = self.password
+ authenticate.tokens.extend(self.tokens)
+ authenticate.opus = True
+ authenticate.client_type = self.client_type
+ self.Log.debug("sending: authenticate: %s", authenticate)
+ self.send_message(PYMUMBLE_MSG_TYPES_AUTHENTICATE, authenticate)
+ except socket.error:
+ self.connected = PYMUMBLE_CONN_STATE_FAILED
+ return self.connected
+
+ self.connected = PYMUMBLE_CONN_STATE_AUTHENTICATING
+ return self.connected
+
+ def loop(self):
+ """
+ Main loop
+ waiting for a message from the server for maximum self.loop_rate time
+ take care of sending the ping
+ take care of sending the queued commands to the server
+ check on every iteration for outgoing sound
+ check for disconnection
+ """
+ self.Log.debug("entering loop")
+ self.exit = False
+
+ last_ping = time.time() # keep track of the last ping time
+
+ # loop as long as the connection and the parent thread are alive
+ while self.connected not in (PYMUMBLE_CONN_STATE_NOT_CONNECTED, PYMUMBLE_CONN_STATE_FAILED) and self.parent_thread.is_alive() and not self.exit:
+ if last_ping + PYMUMBLE_PING_DELAY <= time.time(): # when it is time, send the ping
+ self.ping()
+ last_ping = time.time()
+
+ if self.connected == PYMUMBLE_CONN_STATE_CONNECTED:
+ while self.commands.is_cmd():
+ self.treat_command(self.commands.pop_cmd()) # send the commands coming from the application to the server
+
+ if self.sound_output:
+ self.sound_output.send_audio() # send outgoing audio if available
+
+ (rlist, wlist, xlist) = select.select([self.control_socket], [], [self.control_socket], self.loop_rate) # wait for a socket activity
+
+ if self.control_socket in rlist: # something to be read on the control socket
+ self.read_control_messages()
+ elif self.control_socket in xlist: # socket was closed
+ self.control_socket.close()
+ self.connected = PYMUMBLE_CONN_STATE_NOT_CONNECTED
+
+ def ping(self):
+ """Send the keepalive through available channels"""
+ ping = mumble_pb2.Ping()
+ ping.timestamp = int(time.time())
+ ping.tcp_ping_avg = self.ping_stats['avg']
+ ping.tcp_ping_var = self.ping_stats['var']
+ ping.tcp_packets = self.ping_stats['nb']
+
+ self.Log.debug("sending: ping: %s", ping)
+ self.send_message(PYMUMBLE_MSG_TYPES_PING, ping)
+ self.ping_stats['time_send'] = int(time.time() * 1000)
+ self.Log.debug(self.ping_stats['last_rcv'])
+ if self.ping_stats['last_rcv'] != 0 and int(time.time() * 1000) > self.ping_stats['last_rcv'] + (60 * 1000):
+ self.Log.debug("Ping too long ! Disconnected ?")
+ self.connected = PYMUMBLE_CONN_STATE_NOT_CONNECTED
+
+ def ping_response(self, mess):
+ self.ping_stats['last_rcv'] = int(time.time() * 1000)
+ ping = int(time.time() * 1000) - self.ping_stats['time_send']
+ old_avg = self.ping_stats['avg']
+ nb = self.ping_stats['nb']
+ new_avg = ((self.ping_stats['avg'] * nb) + ping) / (nb + 1)
+
+ try:
+ self.ping_stats['var'] = self.ping_stats['var'] + pow(old_avg - new_avg, 2) + (1 / nb) * pow(ping - new_avg, 2)
+ except ZeroDivisionError:
+ pass
+
+ self.ping_stats['avg'] = new_avg
+ self.ping_stats['nb'] += 1
+
+ def send_message(self, type, message):
+ """Send a control message to the server"""
+ packet = struct.pack("!HL", type, message.ByteSize()) + message.SerializeToString()
+
+ while len(packet) > 0:
+ self.Log.debug("sending message")
+ sent = self.control_socket.send(packet)
+ if sent < 0:
+ raise socket.error("Server socket error")
+ packet = packet[sent:]
+
+ def read_control_messages(self):
+ """Read control messages coming from the server"""
+ # from tools import tohex # for debugging
+
+ try:
+ buffer = self.control_socket.recv(PYMUMBLE_READ_BUFFER_SIZE)
+ self.receive_buffer += buffer
+ except socket.error:
+ pass
+
+ while len(self.receive_buffer) >= 6: # header is present (type + length)
+ self.Log.debug("read control connection")
+ header = self.receive_buffer[0:6]
+
+ if len(header) < 6:
+ break
+
+ (type, size) = struct.unpack("!HL", header) # decode header
+
+ if len(self.receive_buffer) < size + 6: # if not length data, read further
+ break
+
+ # self.Log.debug("message received : " + tohex(self.receive_buffer[0:size+6])) # for debugging
+
+ message = self.receive_buffer[6:size + 6] # get the control message
+ self.receive_buffer = self.receive_buffer[size + 6:] # remove from the buffer the read part
+
+ self.dispatch_control_message(type, message)
+
+ def dispatch_control_message(self, type, message):
+ """Dispatch control messages based on their type"""
+ self.Log.debug("dispatch control message")
+ if type == PYMUMBLE_MSG_TYPES_UDPTUNNEL: # audio encapsulated in control message
+ if self.sound_output:
+ self.sound_received(message)
+
+ elif type == PYMUMBLE_MSG_TYPES_VERSION:
+ mess = mumble_pb2.Version()
+ mess.ParseFromString(message)
+ self.Log.debug("message: Version : %s", mess)
+
+ elif type == PYMUMBLE_MSG_TYPES_AUTHENTICATE:
+ mess = mumble_pb2.Authenticate()
+ mess.ParseFromString(message)
+ self.Log.debug("message: Authenticate : %s", mess)
+
+ elif type == PYMUMBLE_MSG_TYPES_PING:
+ mess = mumble_pb2.Ping()
+ mess.ParseFromString(message)
+ self.Log.debug("message: Ping : %s", mess)
+ self.ping_response(mess)
+
+ elif type == PYMUMBLE_MSG_TYPES_REJECT:
+ mess = mumble_pb2.Reject()
+ mess.ParseFromString(message)
+ self.Log.debug("message: reject : %s", mess)
+ self.connected = PYMUMBLE_CONN_STATE_FAILED
+ self.ready_lock.release()
+ raise ConnectionRejectedError(mess.reason)
+
+ elif type == PYMUMBLE_MSG_TYPES_SERVERSYNC: # this message finish the connection process
+ mess = mumble_pb2.ServerSync()
+ mess.ParseFromString(message)
+ self.Log.debug("message: serversync : %s", mess)
+ self.users.set_myself(mess.session)
+ self.server_max_bandwidth = mess.max_bandwidth
+ self.set_bandwidth(mess.max_bandwidth)
+
+ if self.connected == PYMUMBLE_CONN_STATE_AUTHENTICATING:
+ self.connected = PYMUMBLE_CONN_STATE_CONNECTED
+ self.ready_lock.release() # release the ready-lock
+ self.callbacks(PYMUMBLE_CLBK_CONNECTED)
+
+ elif type == PYMUMBLE_MSG_TYPES_CHANNELREMOVE:
+ mess = mumble_pb2.ChannelRemove()
+ mess.ParseFromString(message)
+ self.Log.debug("message: ChannelRemove : %s", mess)
+
+ self.channels.remove(mess.channel_id)
+
+ elif type == PYMUMBLE_MSG_TYPES_CHANNELSTATE:
+ mess = mumble_pb2.ChannelState()
+ mess.ParseFromString(message)
+ self.Log.debug("message: channelstate : %s", mess)
+
+ self.channels.update(mess)
+
+ elif type == PYMUMBLE_MSG_TYPES_USERREMOVE:
+ mess = mumble_pb2.UserRemove()
+ mess.ParseFromString(message)
+ self.Log.debug("message: UserRemove : %s", mess)
+
+ self.users.remove(mess)
+
+ elif type == PYMUMBLE_MSG_TYPES_USERSTATE:
+ mess = mumble_pb2.UserState()
+ mess.ParseFromString(message)
+ self.Log.debug("message: userstate : %s", mess)
+
+ self.users.update(mess)
+
+ elif type == PYMUMBLE_MSG_TYPES_BANLIST:
+ mess = mumble_pb2.BanList()
+ mess.ParseFromString(message)
+ self.Log.debug("message: BanList : %s", mess)
+
+ elif type == PYMUMBLE_MSG_TYPES_TEXTMESSAGE:
+ mess = mumble_pb2.TextMessage()
+ mess.ParseFromString(message)
+ self.Log.debug("message: TextMessage : %s", mess)
+
+ self.callbacks(PYMUMBLE_CLBK_TEXTMESSAGERECEIVED, mess)
+
+ elif type == PYMUMBLE_MSG_TYPES_PERMISSIONDENIED:
+ mess = mumble_pb2.PermissionDenied()
+ mess.ParseFromString(message)
+ self.Log.debug("message: PermissionDenied : %s", mess)
+
+ self.callbacks(PYMUMBLE_CLBK_PERMISSIONDENIED, mess)
+
+ elif type == PYMUMBLE_MSG_TYPES_ACL:
+ mess = mumble_pb2.ACL()
+ mess.ParseFromString(message)
+ self.Log.debug("message: ACL : %s", mess)
+ self.channels[mess.channel_id].update_acl(mess)
+ self.callbacks(PYMUMBLE_CLBK_ACLRECEIVED, mess)
+
+ elif type == PYMUMBLE_MSG_TYPES_QUERYUSERS:
+ mess = mumble_pb2.QueryUsers()
+ mess.ParseFromString(message)
+ self.Log.debug("message: QueryUsers : %s", mess)
+
+ elif type == PYMUMBLE_MSG_TYPES_CRYPTSETUP:
+ mess = mumble_pb2.CryptSetup()
+ mess.ParseFromString(message)
+ self.Log.debug("message: CryptSetup : %s", mess)
+ self.ping()
+
+ elif type == PYMUMBLE_MSG_TYPES_CONTEXTACTIONMODIFY:
+ mess = mumble_pb2.ContextActionModify()
+ mess.ParseFromString(message)
+ self.Log.debug("message: ContextActionModify : %s", mess)
+
+ self.callbacks(PYMUMBLE_CLBK_CONTEXTACTIONRECEIVED, mess)
+
+ elif type == PYMUMBLE_MSG_TYPES_CONTEXTACTION:
+ mess = mumble_pb2.ContextAction()
+ mess.ParseFromString(message)
+ self.Log.debug("message: ContextAction : %s", mess)
+
+ elif type == PYMUMBLE_MSG_TYPES_USERLIST:
+ mess = mumble_pb2.UserList()
+ mess.ParseFromString(message)
+ self.Log.debug("message: UserList : %s", mess)
+
+ elif type == PYMUMBLE_MSG_TYPES_VOICETARGET:
+ mess = mumble_pb2.VoiceTarget()
+ mess.ParseFromString(message)
+ self.Log.debug("message: VoiceTarget : %s", mess)
+
+ elif type == PYMUMBLE_MSG_TYPES_PERMISSIONQUERY:
+ mess = mumble_pb2.PermissionQuery()
+ mess.ParseFromString(message)
+ self.Log.debug("message: PermissionQuery : %s", mess)
+
+ elif type == PYMUMBLE_MSG_TYPES_CODECVERSION:
+ mess = mumble_pb2.CodecVersion()
+ mess.ParseFromString(message)
+ self.Log.debug("message: CodecVersion : %s", mess)
+ if self.sound_output:
+ self.sound_output.set_default_codec(mess)
+
+ elif type == PYMUMBLE_MSG_TYPES_USERSTATS:
+ mess = mumble_pb2.UserStats()
+ mess.ParseFromString(message)
+ self.Log.debug("message: UserStats : %s", mess)
+
+ elif type == PYMUMBLE_MSG_TYPES_REQUESTBLOB:
+ mess = mumble_pb2.RequestBlob()
+ mess.ParseFromString(message)
+ self.Log.debug("message: RequestBlob : %s", mess)
+
+ elif type == PYMUMBLE_MSG_TYPES_SERVERCONFIG:
+ mess = mumble_pb2.ServerConfig()
+ mess.ParseFromString(message)
+ self.Log.debug("message: ServerConfig : %s", mess)
+ for line in str(mess).split('\n'):
+ items = line.split(':')
+ if len(items) != 2:
+ continue
+ if items[0] == 'allow_html':
+ self.server_allow_html = items[1].strip() == 'true'
+ elif items[0] == 'message_length':
+ self.server_max_message_length = int(items[1].strip())
+ elif items[0] == 'image_message_length':
+ self.server_max_image_message_length = int(items[1].strip())
+
+ def set_bandwidth(self, bandwidth):
+ """Set the total allowed outgoing bandwidth"""
+ if self.server_max_bandwidth is not None and bandwidth > self.server_max_bandwidth:
+ self.bandwidth = self.server_max_bandwidth
+ else:
+ self.bandwidth = bandwidth
+
+ if self.sound_output:
+ self.sound_output.set_bandwidth(self.bandwidth) # communicate the update to the outgoing audio manager
+
+ def sound_received(self, message):
+ """Manage a received sound message"""
+ # from tools import tohex # for debugging
+
+ pos = 0
+
+ # self.Log.debug("sound packet : " + tohex(message)) # for debugging
+ (header,) = struct.unpack("!B", bytes([message[pos]])) # extract the header
+ type = (header & 0b11100000) >> 5
+ target = header & 0b00011111
+ pos += 1
+
+ if type == PYMUMBLE_AUDIO_TYPE_PING:
+ return
+
+ session = tools.VarInt() # decode session id
+ pos += session.decode(message[pos:pos + 10])
+
+ sequence = tools.VarInt() # decode sequence number
+ pos += sequence.decode(message[pos:pos + 10])
+
+ self.Log.debug("audio packet received from %i, sequence %i, type:%i, target:%i, length:%i", session.value, sequence.value, type, target, len(message))
+
+ terminator = False # set to true if it's the last 10 ms audio frame for the packet (used with CELT codec)
+ while (pos < len(message)) and not terminator: # get the audio frames one by one
+ if type == PYMUMBLE_AUDIO_TYPE_OPUS:
+ size = tools.VarInt() # OPUS use varint for the frame length
+
+ pos += size.decode(message[pos:pos + 10])
+ size = size.value
+
+ if not (size & 0x2000): # terminator is 0x2000 in the resulting int.
+ terminator = True # should actually always be 0 as OPUS can use variable length audio frames
+
+ size &= 0x1fff # isolate the size from the terminator
+ else:
+ (header,) = struct.unpack("!B", message[pos:pos + 1]) # CELT length and terminator is encoded in a 1 byte int
+ if not (header & 0b10000000):
+ terminator = True
+ size = header & 0b01111111
+ pos += 1
+
+ self.Log.debug("Audio frame : time:%f, last:%s, size:%i, type:%i, target:%i, pos:%i", time.time(), str(terminator), size, type, target, pos - 1)
+
+ if size > 0:
+ try:
+ newsound = self.users[session.value].sound.add(message[pos:pos + size],
+ sequence.value,
+ type,
+ target) # add the sound to the user's sound queue
+
+ if newsound is None: # In case audio have been disable for specific users
+ return
+
+ self.callbacks(PYMUMBLE_CLBK_SOUNDRECEIVED, self.users[session.value], newsound)
+
+ sequence.value += int(round(newsound.duration / 1000 * 10)) # add 1 sequence per 10ms of audio
+
+ self.Log.debug("Audio frame : time:%f last:%s, size:%i, uncompressed:%i, type:%i, target:%i", time.time(), str(terminator), size, newsound.size, type, target)
+ except CodecNotSupportedError as msg:
+ print(msg)
+ except KeyError: # sound received after user removed
+ pass
+
+ # if len(message) - pos < size:
+ # raise InvalidFormatError("Invalid audio frame size")
+
+ pos += size # go further in the packet, after the audio frame
+ # TODO: get position info
+
+ def set_application_string(self, string):
+ """Set the application name, that can be viewed by other clients on the server"""
+ self.application = string
+
+ def set_loop_rate(self, rate):
+ """Set the current main loop rate (pause per iteration)"""
+ self.loop_rate = rate
+
+ def get_loop_rate(self):
+ """Get the current main loop rate (pause per iteration)"""
+ return self.loop_rate
+
+ def set_codec_profile(self, profile):
+ """set the audio profile"""
+ if profile in ["audio", "voip", "restricted_lowdelay"]:
+ self.__opus_profile = profile
+ else:
+ raise ValueError("Unknown profile: " + str(profile))
+
+ def get_codec_profile(self):
+ """return the audio profile string"""
+ return self.__opus_profile
+
+ def set_receive_sound(self, value):
+ """Enable or disable the management of incoming sounds"""
+ if value:
+ self.receive_sound = True
+ else:
+ self.receive_sound = False
+
+ def is_ready(self):
+ """Wait for the connection to be fully completed. To be used in the main thread"""
+ self.ready_lock.acquire()
+ self.ready_lock.release()
+
+ def execute_command(self, cmd, blocking=True):
+ """Create a command to be sent to the server. To be used in the main thread"""
+ self.is_ready()
+
+ lock = self.commands.new_cmd(cmd)
+ if blocking and self.mumble_thread is not threading.current_thread():
+ lock.acquire()
+ lock.release()
+
+ return lock
+
+ # TODO: manage a timeout for blocking commands. Currently, no command actually waits for the server to execute
+ # The result of these commands should actually be checked against incoming server updates
+
+ def treat_command(self, cmd):
+ """Send the awaiting commands to the server. Used in the pymumble thread."""
+ if cmd.cmd == PYMUMBLE_CMD_MOVE:
+ userstate = mumble_pb2.UserState()
+ userstate.session = cmd.parameters["session"]
+ userstate.channel_id = cmd.parameters["channel_id"]
+ self.Log.debug("Moving to channel")
+ self.send_message(PYMUMBLE_MSG_TYPES_USERSTATE, userstate)
+ cmd.response = True
+ self.commands.answer(cmd)
+ elif cmd.cmd == PYMUMBLE_CMD_TEXTMESSAGE:
+ textmessage = mumble_pb2.TextMessage()
+ textmessage.session.append(cmd.parameters["session"])
+ textmessage.channel_id.append(cmd.parameters["channel_id"])
+ textmessage.message = cmd.parameters["message"]
+ self.send_message(PYMUMBLE_MSG_TYPES_TEXTMESSAGE, textmessage)
+ cmd.response = True
+ self.commands.answer(cmd)
+ elif cmd.cmd == PYMUMBLE_CMD_TEXTPRIVATEMESSAGE:
+ textprivatemessage = mumble_pb2.TextMessage()
+ textprivatemessage.session.append(cmd.parameters["session"])
+ textprivatemessage.message = cmd.parameters["message"]
+ self.send_message(PYMUMBLE_MSG_TYPES_TEXTMESSAGE, textprivatemessage)
+ cmd.response = True
+ self.commands.answer(cmd)
+ elif cmd.cmd == PYMUMBLE_MSG_TYPES_CHANNELSTATE:
+ channelstate = mumble_pb2.ChannelState()
+ channelstate.parent = cmd.parameters["parent"]
+ channelstate.name = cmd.parameters["name"]
+ channelstate.temporary = cmd.parameters["temporary"]
+ self.send_message(PYMUMBLE_MSG_TYPES_CHANNELSTATE, channelstate)
+ cmd.response = True
+ self.commands.answer(cmd)
+ elif cmd.cmd == PYMUMBLE_MSG_TYPES_CHANNELREMOVE:
+ channelremove = mumble_pb2.ChannelRemove()
+ channelremove.channel_id = cmd.parameters["channel_id"]
+ self.send_message(PYMUMBLE_MSG_TYPES_CHANNELREMOVE, channelremove)
+ cmd.response = True
+ self.commands.answer(cmd)
+ elif cmd.cmd == PYMUMBLE_CMD_UPDATECHANNEL:
+ channelstate = mumble_pb2.ChannelState()
+ for key, value in cmd.parameters.items():
+ setattr(channelstate, key, value)
+ self.send_message(PYMUMBLE_MSG_TYPES_CHANNELSTATE, channelstate)
+ cmd.response = True
+ self.commands.answer(cmd)
+ elif cmd.cmd == PYMUMBLE_CMD_LINKCHANNEL:
+ channelstate = mumble_pb2.ChannelState()
+ channelstate.channel_id = cmd.parameters["channel_id"]
+ channelstate.links_add.append(cmd.parameters["add_id"])
+ self.send_message(PYMUMBLE_MSG_TYPES_CHANNELSTATE, channelstate)
+ cmd.response = True
+ self.commands.answer(cmd)
+ elif cmd.cmd == PYMUMBLE_CMD_UNLINKCHANNEL:
+ channelstate = mumble_pb2.ChannelState()
+ channelstate.channel_id = cmd.parameters["channel_id"]
+ for remove_id in cmd.parameters["remove_ids"]:
+ channelstate.links_remove.append(remove_id)
+ self.send_message(PYMUMBLE_MSG_TYPES_CHANNELSTATE, channelstate)
+ cmd.response = True
+ self.commands.answer(cmd)
+ elif cmd.cmd == PYMUMBLE_MSG_TYPES_VOICETARGET:
+ textvoicetarget = mumble_pb2.VoiceTarget()
+ textvoicetarget.id = cmd.parameters["id"]
+ targets = []
+ if cmd.parameters["id"] == 1:
+ voicetarget = mumble_pb2.VoiceTarget.Target()
+ voicetarget.channel_id = cmd.parameters["targets"][0]
+ targets.append(voicetarget)
+ else:
+ for target in cmd.parameters["targets"]:
+ voicetarget = mumble_pb2.VoiceTarget.Target()
+ voicetarget.session.append(target)
+ targets.append(voicetarget)
+ textvoicetarget.targets.extend(targets)
+ self.send_message(PYMUMBLE_MSG_TYPES_VOICETARGET, textvoicetarget)
+ cmd.response = True
+ self.commands.answer(cmd)
+ elif cmd.cmd == PYMUMBLE_CMD_MODUSERSTATE:
+ userstate = mumble_pb2.UserState()
+ userstate.session = cmd.parameters["session"]
+
+ if "mute" in cmd.parameters:
+ userstate.mute = cmd.parameters["mute"]
+ if "self_mute" in cmd.parameters:
+ userstate.self_mute = cmd.parameters["self_mute"]
+ if "deaf" in cmd.parameters:
+ userstate.deaf = cmd.parameters["deaf"]
+ if "self_deaf" in cmd.parameters:
+ userstate.self_deaf = cmd.parameters["self_deaf"]
+ if "suppress" in cmd.parameters:
+ userstate.suppress = cmd.parameters["suppress"]
+ if "recording" in cmd.parameters:
+ userstate.recording = cmd.parameters["recording"]
+ if "comment" in cmd.parameters:
+ userstate.comment = cmd.parameters["comment"]
+ if "texture" in cmd.parameters:
+ userstate.texture = cmd.parameters["texture"]
+ if "user_id" in cmd.parameters:
+ userstate.user_id = cmd.parameters["user_id"]
+ if "plugin_context" in cmd.parameters:
+ userstate.plugin_context = cmd.parameters["plugin_context"]
+ if "listening_channel_add" in cmd.parameters:
+ userstate.listening_channel_add.extend(cmd.parameters["listening_channel_add"])
+ if "listening_channel_remove" in cmd.parameters:
+ userstate.listening_channel_remove.extend(cmd.parameters["listening_channel_remove"])
+
+ self.send_message(PYMUMBLE_MSG_TYPES_USERSTATE, userstate)
+ cmd.response = True
+ self.commands.answer(cmd)
+ elif cmd.cmd == PYMUMBLE_CMD_REMOVEUSER:
+ userremove = mumble_pb2.UserRemove()
+ userremove.session = cmd.parameters["session"]
+ userremove.reason = cmd.parameters["reason"]
+ userremove.ban = cmd.parameters["ban"]
+ self.send_message(PYMUMBLE_MSG_TYPES_USERREMOVE, userremove)
+ cmd.response = True
+ self.commands.answer(cmd)
+ elif cmd.cmd == PYMUMBLE_CMD_QUERYACL:
+ acl = mumble_pb2.ACL()
+ acl.channel_id = cmd.parameters["channel_id"]
+ acl.query = True
+ self.send_message(PYMUMBLE_MSG_TYPES_ACL, acl)
+ cmd.response = True
+ self.commands.answer(cmd)
+ elif cmd.cmd == PYMUMBLE_CMD_UPDATEACL:
+ acl = mumble_pb2.ACL()
+ acl.channel_id = cmd.parameters["channel_id"]
+ acl.inherit_acls = cmd.parameters["inherit_acls"]
+
+ for msg_group in cmd.parameters["chan_group"]:
+ chan_group = mumble_pb2.ACL.ChanGroup()
+ chan_group.name = msg_group['name']
+ if msg_group['inherited'] is not None:
+ chan_group.inherited = msg_group['inherited']
+ if msg_group['inherit'] is not None:
+ chan_group.inherit = msg_group['inherit']
+ if msg_group['inheritable'] is not None:
+ chan_group.inheritable = msg_group['inheritable']
+ for add_id in msg_group['add']:
+ chan_group.add.append(add_id)
+ for remove_id in msg_group['remove']:
+ chan_group.remove.append(remove_id)
+ acl.groups.append(chan_group)
+
+ for msg_acl in cmd.parameters["chan_acl"]:
+ chan_acl = mumble_pb2.ACL.ChanACL()
+ if msg_acl['apply_here'] is not None:
+ chan_acl.apply_here = msg_acl['apply_here']
+ if msg_acl['apply_subs'] is not None:
+ chan_acl.apply_subs = msg_acl['apply_subs']
+ if msg_acl['inherited'] is not None:
+ chan_acl.inherited = msg_acl['inherited']
+ if msg_acl['user_id'] is not None:
+ chan_acl.user_id = msg_acl['user_id']
+ if msg_acl['group'] is not None:
+ chan_acl.group = msg_acl['group']
+ if msg_acl['grant'] is not None:
+ chan_acl.grant = msg_acl['grant']
+ if msg_acl['deny'] is not None:
+ chan_acl.deny = msg_acl['deny']
+
+ if not chan_acl.inherited:
+ acl.acls.append(chan_acl)
+
+ acl.query = False
+ self.send_message(PYMUMBLE_MSG_TYPES_ACL, acl)
+ cmd.response = True
+ self.commands.answer(cmd)
+
+ def get_max_message_length(self):
+ return self.server_max_message_length
+
+ def get_max_image_length(self):
+ return self.server_max_image_message_length
+
+ def my_channel(self):
+ return self.channels[self.users.myself["channel_id"]]
+
+ def denial_type(self, n):
+ return mumble_pb2.PermissionDenied.DenyType.Name(n)
+
+ def stop(self):
+ self.reconnect = None
+ self.exit = True
+ self.control_socket.close()
diff --git a/pymumble_py3/mumble_pb2.py b/pymumble_py3/mumble_pb2.py
new file mode 100644
index 0000000..deda1b4
--- /dev/null
+++ b/pymumble_py3/mumble_pb2.py
@@ -0,0 +1,2589 @@
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: Mumble.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+ name='Mumble.proto',
+ package='MumbleProto',
+ syntax='proto2',
+ serialized_options=_b('H\001'),
+ serialized_pb=_b('\n\x0cMumble.proto\x12\x0bMumbleProto\"b\n\x07Version\x12\x12\n\nversion_v1\x18\x01 \x01(\r\x12\x12\n\nversion_v2\x18\x05 \x01(\x04\x12\x0f\n\x07release\x18\x02 \x01(\t\x12\n\n\x02os\x18\x03 \x01(\t\x12\x12\n\nos_version\x18\x04 \x01(\t\"\x1b\n\tUDPTunnel\x12\x0e\n\x06packet\x18\x01 \x02(\x0c\"\x86\x01\n\x0c\x41uthenticate\x12\x10\n\x08username\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\x12\x0e\n\x06tokens\x18\x03 \x03(\t\x12\x15\n\rcelt_versions\x18\x04 \x03(\x05\x12\x13\n\x04opus\x18\x05 \x01(\x08:\x05\x66\x61lse\x12\x16\n\x0b\x63lient_type\x18\x06 \x01(\x05:\x01\x30\"\xd5\x01\n\x04Ping\x12\x11\n\ttimestamp\x18\x01 \x01(\x04\x12\x0c\n\x04good\x18\x02 \x01(\r\x12\x0c\n\x04late\x18\x03 \x01(\r\x12\x0c\n\x04lost\x18\x04 \x01(\r\x12\x0e\n\x06resync\x18\x05 \x01(\r\x12\x13\n\x0budp_packets\x18\x06 \x01(\r\x12\x13\n\x0btcp_packets\x18\x07 \x01(\r\x12\x14\n\x0cudp_ping_avg\x18\x08 \x01(\x02\x12\x14\n\x0cudp_ping_var\x18\t \x01(\x02\x12\x14\n\x0ctcp_ping_avg\x18\n \x01(\x02\x12\x14\n\x0ctcp_ping_var\x18\x0b \x01(\x02\"\xf7\x01\n\x06Reject\x12,\n\x04type\x18\x01 \x01(\x0e\x32\x1e.MumbleProto.Reject.RejectType\x12\x0e\n\x06reason\x18\x02 \x01(\t\"\xae\x01\n\nRejectType\x12\x08\n\x04None\x10\x00\x12\x10\n\x0cWrongVersion\x10\x01\x12\x13\n\x0fInvalidUsername\x10\x02\x12\x0f\n\x0bWrongUserPW\x10\x03\x12\x11\n\rWrongServerPW\x10\x04\x12\x11\n\rUsernameInUse\x10\x05\x12\x0e\n\nServerFull\x10\x06\x12\x11\n\rNoCertificate\x10\x07\x12\x15\n\x11\x41uthenticatorFail\x10\x08\"_\n\nServerSync\x12\x0f\n\x07session\x18\x01 \x01(\r\x12\x15\n\rmax_bandwidth\x18\x02 \x01(\r\x12\x14\n\x0cwelcome_text\x18\x03 \x01(\t\x12\x13\n\x0bpermissions\x18\x04 \x01(\x04\"#\n\rChannelRemove\x12\x12\n\nchannel_id\x18\x01 \x02(\r\"\x99\x02\n\x0c\x43hannelState\x12\x12\n\nchannel_id\x18\x01 \x01(\r\x12\x0e\n\x06parent\x18\x02 \x01(\r\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\r\n\x05links\x18\x04 \x03(\r\x12\x13\n\x0b\x64\x65scription\x18\x05 \x01(\t\x12\x11\n\tlinks_add\x18\x06 \x03(\r\x12\x14\n\x0clinks_remove\x18\x07 \x03(\r\x12\x18\n\ttemporary\x18\x08 \x01(\x08:\x05\x66\x61lse\x12\x13\n\x08position\x18\t \x01(\x05:\x01\x30\x12\x18\n\x10\x64\x65scription_hash\x18\n \x01(\x0c\x12\x11\n\tmax_users\x18\x0b \x01(\r\x12\x1b\n\x13is_enter_restricted\x18\x0c \x01(\x08\x12\x11\n\tcan_enter\x18\r \x01(\x08\"I\n\nUserRemove\x12\x0f\n\x07session\x18\x01 \x02(\r\x12\r\n\x05\x61\x63tor\x18\x02 \x01(\r\x12\x0e\n\x06reason\x18\x03 \x01(\t\x12\x0b\n\x03\x62\x61n\x18\x04 \x01(\x08\"\xe6\x04\n\tUserState\x12\x0f\n\x07session\x18\x01 \x01(\r\x12\r\n\x05\x61\x63tor\x18\x02 \x01(\r\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x0f\n\x07user_id\x18\x04 \x01(\r\x12\x12\n\nchannel_id\x18\x05 \x01(\r\x12\x0c\n\x04mute\x18\x06 \x01(\x08\x12\x0c\n\x04\x64\x65\x61\x66\x18\x07 \x01(\x08\x12\x10\n\x08suppress\x18\x08 \x01(\x08\x12\x11\n\tself_mute\x18\t \x01(\x08\x12\x11\n\tself_deaf\x18\n \x01(\x08\x12\x0f\n\x07texture\x18\x0b \x01(\x0c\x12\x16\n\x0eplugin_context\x18\x0c \x01(\x0c\x12\x17\n\x0fplugin_identity\x18\r \x01(\t\x12\x0f\n\x07\x63omment\x18\x0e \x01(\t\x12\x0c\n\x04hash\x18\x0f \x01(\t\x12\x14\n\x0c\x63omment_hash\x18\x10 \x01(\x0c\x12\x14\n\x0ctexture_hash\x18\x11 \x01(\x0c\x12\x18\n\x10priority_speaker\x18\x12 \x01(\x08\x12\x11\n\trecording\x18\x13 \x01(\x08\x12\x1f\n\x17temporary_access_tokens\x18\x14 \x03(\t\x12\x1d\n\x15listening_channel_add\x18\x15 \x03(\r\x12 \n\x18listening_channel_remove\x18\x16 \x03(\r\x12L\n\x1blistening_volume_adjustment\x18\x17 \x03(\x0b\x32\'.MumbleProto.UserState.VolumeAdjustment\x1aH\n\x10VolumeAdjustment\x12\x19\n\x11listening_channel\x18\x01 \x01(\r\x12\x19\n\x11volume_adjustment\x18\x02 \x01(\x02\"\xc4\x01\n\x07\x42\x61nList\x12+\n\x04\x62\x61ns\x18\x01 \x03(\x0b\x32\x1d.MumbleProto.BanList.BanEntry\x12\x14\n\x05query\x18\x02 \x01(\x08:\x05\x66\x61lse\x1av\n\x08\x42\x61nEntry\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x02(\x0c\x12\x0c\n\x04mask\x18\x02 \x02(\r\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x0c\n\x04hash\x18\x04 \x01(\t\x12\x0e\n\x06reason\x18\x05 \x01(\t\x12\r\n\x05start\x18\x06 \x01(\t\x12\x10\n\x08\x64uration\x18\x07 \x01(\r\"c\n\x0bTextMessage\x12\r\n\x05\x61\x63tor\x18\x01 \x01(\r\x12\x0f\n\x07session\x18\x02 \x03(\r\x12\x12\n\nchannel_id\x18\x03 \x03(\r\x12\x0f\n\x07tree_id\x18\x04 \x03(\r\x12\x0f\n\x07message\x18\x05 \x02(\t\"\xa7\x03\n\x10PermissionDenied\x12\x12\n\npermission\x18\x01 \x01(\r\x12\x12\n\nchannel_id\x18\x02 \x01(\r\x12\x0f\n\x07session\x18\x03 \x01(\r\x12\x0e\n\x06reason\x18\x04 \x01(\t\x12\x34\n\x04type\x18\x05 \x01(\x0e\x32&.MumbleProto.PermissionDenied.DenyType\x12\x0c\n\x04name\x18\x06 \x01(\t\"\x85\x02\n\x08\x44\x65nyType\x12\x08\n\x04Text\x10\x00\x12\x0e\n\nPermission\x10\x01\x12\r\n\tSuperUser\x10\x02\x12\x0f\n\x0b\x43hannelName\x10\x03\x12\x0f\n\x0bTextTooLong\x10\x04\x12\x07\n\x03H9K\x10\x05\x12\x14\n\x10TemporaryChannel\x10\x06\x12\x16\n\x12MissingCertificate\x10\x07\x12\x0c\n\x08UserName\x10\x08\x12\x0f\n\x0b\x43hannelFull\x10\t\x12\x10\n\x0cNestingLimit\x10\n\x12\x15\n\x11\x43hannelCountLimit\x10\x0b\x12\x18\n\x14\x43hannelListenerLimit\x10\x0c\x12\x15\n\x11UserListenerLimit\x10\r\"\xd4\x03\n\x03\x41\x43L\x12\x12\n\nchannel_id\x18\x01 \x02(\r\x12\x1a\n\x0cinherit_acls\x18\x02 \x01(\x08:\x04true\x12*\n\x06groups\x18\x03 \x03(\x0b\x32\x1a.MumbleProto.ACL.ChanGroup\x12&\n\x04\x61\x63ls\x18\x04 \x03(\x0b\x32\x18.MumbleProto.ACL.ChanACL\x12\x14\n\x05query\x18\x05 \x01(\x08:\x05\x66\x61lse\x1a\x9c\x01\n\tChanGroup\x12\x0c\n\x04name\x18\x01 \x02(\t\x12\x17\n\tinherited\x18\x02 \x01(\x08:\x04true\x12\x15\n\x07inherit\x18\x03 \x01(\x08:\x04true\x12\x19\n\x0binheritable\x18\x04 \x01(\x08:\x04true\x12\x0b\n\x03\x61\x64\x64\x18\x05 \x03(\r\x12\x0e\n\x06remove\x18\x06 \x03(\r\x12\x19\n\x11inherited_members\x18\x07 \x03(\r\x1a\x93\x01\n\x07\x43hanACL\x12\x18\n\napply_here\x18\x01 \x01(\x08:\x04true\x12\x18\n\napply_subs\x18\x02 \x01(\x08:\x04true\x12\x17\n\tinherited\x18\x03 \x01(\x08:\x04true\x12\x0f\n\x07user_id\x18\x04 \x01(\r\x12\r\n\x05group\x18\x05 \x01(\t\x12\r\n\x05grant\x18\x06 \x01(\r\x12\x0c\n\x04\x64\x65ny\x18\x07 \x01(\r\"(\n\nQueryUsers\x12\x0b\n\x03ids\x18\x01 \x03(\r\x12\r\n\x05names\x18\x02 \x03(\t\"E\n\nCryptSetup\x12\x0b\n\x03key\x18\x01 \x01(\x0c\x12\x14\n\x0c\x63lient_nonce\x18\x02 \x01(\x0c\x12\x14\n\x0cserver_nonce\x18\x03 \x01(\x0c\"\xd3\x01\n\x13\x43ontextActionModify\x12\x0e\n\x06\x61\x63tion\x18\x01 \x02(\t\x12\x0c\n\x04text\x18\x02 \x01(\t\x12\x0f\n\x07\x63ontext\x18\x03 \x01(\r\x12=\n\toperation\x18\x04 \x01(\x0e\x32*.MumbleProto.ContextActionModify.Operation\",\n\x07\x43ontext\x12\n\n\x06Server\x10\x01\x12\x0b\n\x07\x43hannel\x10\x02\x12\x08\n\x04User\x10\x04\" \n\tOperation\x12\x07\n\x03\x41\x64\x64\x10\x00\x12\n\n\x06Remove\x10\x01\"D\n\rContextAction\x12\x0f\n\x07session\x18\x01 \x01(\r\x12\x12\n\nchannel_id\x18\x02 \x01(\r\x12\x0e\n\x06\x61\x63tion\x18\x03 \x02(\t\"\x85\x01\n\x08UserList\x12)\n\x05users\x18\x01 \x03(\x0b\x32\x1a.MumbleProto.UserList.User\x1aN\n\x04User\x12\x0f\n\x07user_id\x18\x01 \x02(\r\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x11\n\tlast_seen\x18\x03 \x01(\t\x12\x14\n\x0clast_channel\x18\x04 \x01(\r\"\xb8\x01\n\x0bVoiceTarget\x12\n\n\x02id\x18\x01 \x01(\r\x12\x30\n\x07targets\x18\x02 \x03(\x0b\x32\x1f.MumbleProto.VoiceTarget.Target\x1ak\n\x06Target\x12\x0f\n\x07session\x18\x01 \x03(\r\x12\x12\n\nchannel_id\x18\x02 \x01(\r\x12\r\n\x05group\x18\x03 \x01(\t\x12\x14\n\x05links\x18\x04 \x01(\x08:\x05\x66\x61lse\x12\x17\n\x08\x63hildren\x18\x05 \x01(\x08:\x05\x66\x61lse\"P\n\x0fPermissionQuery\x12\x12\n\nchannel_id\x18\x01 \x01(\r\x12\x13\n\x0bpermissions\x18\x02 \x01(\r\x12\x14\n\x05\x66lush\x18\x03 \x01(\x08:\x05\x66\x61lse\"\\\n\x0c\x43odecVersion\x12\r\n\x05\x61lpha\x18\x01 \x02(\x05\x12\x0c\n\x04\x62\x65ta\x18\x02 \x02(\x05\x12\x1a\n\x0cprefer_alpha\x18\x03 \x02(\x08:\x04true\x12\x13\n\x04opus\x18\x04 \x01(\x08:\x05\x66\x61lse\"\xb8\x04\n\tUserStats\x12\x0f\n\x07session\x18\x01 \x01(\r\x12\x19\n\nstats_only\x18\x02 \x01(\x08:\x05\x66\x61lse\x12\x14\n\x0c\x63\x65rtificates\x18\x03 \x03(\x0c\x12\x31\n\x0b\x66rom_client\x18\x04 \x01(\x0b\x32\x1c.MumbleProto.UserStats.Stats\x12\x31\n\x0b\x66rom_server\x18\x05 \x01(\x0b\x32\x1c.MumbleProto.UserStats.Stats\x12\x13\n\x0budp_packets\x18\x06 \x01(\r\x12\x13\n\x0btcp_packets\x18\x07 \x01(\r\x12\x14\n\x0cudp_ping_avg\x18\x08 \x01(\x02\x12\x14\n\x0cudp_ping_var\x18\t \x01(\x02\x12\x14\n\x0ctcp_ping_avg\x18\n \x01(\x02\x12\x14\n\x0ctcp_ping_var\x18\x0b \x01(\x02\x12%\n\x07version\x18\x0c \x01(\x0b\x32\x14.MumbleProto.Version\x12\x15\n\rcelt_versions\x18\r \x03(\x05\x12\x0f\n\x07\x61\x64\x64ress\x18\x0e \x01(\x0c\x12\x11\n\tbandwidth\x18\x0f \x01(\r\x12\x12\n\nonlinesecs\x18\x10 \x01(\r\x12\x10\n\x08idlesecs\x18\x11 \x01(\r\x12!\n\x12strong_certificate\x18\x12 \x01(\x08:\x05\x66\x61lse\x12\x13\n\x04opus\x18\x13 \x01(\x08:\x05\x66\x61lse\x1a\x41\n\x05Stats\x12\x0c\n\x04good\x18\x01 \x01(\r\x12\x0c\n\x04late\x18\x02 \x01(\r\x12\x0c\n\x04lost\x18\x03 \x01(\r\x12\x0e\n\x06resync\x18\x04 \x01(\r\"\\\n\x0bRequestBlob\x12\x17\n\x0fsession_texture\x18\x01 \x03(\r\x12\x17\n\x0fsession_comment\x18\x02 \x03(\r\x12\x1b\n\x13\x63hannel_description\x18\x03 \x03(\r\"\xb3\x01\n\x0cServerConfig\x12\x15\n\rmax_bandwidth\x18\x01 \x01(\r\x12\x14\n\x0cwelcome_text\x18\x02 \x01(\t\x12\x12\n\nallow_html\x18\x03 \x01(\x08\x12\x16\n\x0emessage_length\x18\x04 \x01(\r\x12\x1c\n\x14image_message_length\x18\x05 \x01(\r\x12\x11\n\tmax_users\x18\x06 \x01(\r\x12\x19\n\x11recording_allowed\x18\x07 \x01(\x08\"a\n\rSuggestConfig\x12\x12\n\nversion_v1\x18\x01 \x01(\r\x12\x12\n\nversion_v2\x18\x04 \x01(\x04\x12\x12\n\npositional\x18\x02 \x01(\x08\x12\x14\n\x0cpush_to_talk\x18\x03 \x01(\x08\"k\n\x16PluginDataTransmission\x12\x15\n\rsenderSession\x18\x01 \x01(\r\x12\x1c\n\x10receiverSessions\x18\x02 \x03(\rB\x02\x10\x01\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c\x12\x0e\n\x06\x64\x61taID\x18\x04 \x01(\tB\x02H\x01')
+)
+
+
+
+_REJECT_REJECTTYPE = _descriptor.EnumDescriptor(
+ name='RejectType',
+ full_name='MumbleProto.Reject.RejectType',
+ filename=None,
+ file=DESCRIPTOR,
+ values=[
+ _descriptor.EnumValueDescriptor(
+ name='None', index=0, number=0,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='WrongVersion', index=1, number=1,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='InvalidUsername', index=2, number=2,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='WrongUserPW', index=3, number=3,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='WrongServerPW', index=4, number=4,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='UsernameInUse', index=5, number=5,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='ServerFull', index=6, number=6,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='NoCertificate', index=7, number=7,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='AuthenticatorFail', index=8, number=8,
+ serialized_options=None,
+ type=None),
+ ],
+ containing_type=None,
+ serialized_options=None,
+ serialized_start=585,
+ serialized_end=759,
+)
+_sym_db.RegisterEnumDescriptor(_REJECT_REJECTTYPE)
+
+_PERMISSIONDENIED_DENYTYPE = _descriptor.EnumDescriptor(
+ name='DenyType',
+ full_name='MumbleProto.PermissionDenied.DenyType',
+ filename=None,
+ file=DESCRIPTOR,
+ values=[
+ _descriptor.EnumValueDescriptor(
+ name='Text', index=0, number=0,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='Permission', index=1, number=1,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='SuperUser', index=2, number=2,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='ChannelName', index=3, number=3,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='TextTooLong', index=4, number=4,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='H9K', index=5, number=5,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='TemporaryChannel', index=6, number=6,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='MissingCertificate', index=7, number=7,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='UserName', index=8, number=8,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='ChannelFull', index=9, number=9,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='NestingLimit', index=10, number=10,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='ChannelCountLimit', index=11, number=11,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='ChannelListenerLimit', index=12, number=12,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='UserListenerLimit', index=13, number=13,
+ serialized_options=None,
+ type=None),
+ ],
+ containing_type=None,
+ serialized_options=None,
+ serialized_start=2334,
+ serialized_end=2595,
+)
+_sym_db.RegisterEnumDescriptor(_PERMISSIONDENIED_DENYTYPE)
+
+_CONTEXTACTIONMODIFY_CONTEXT = _descriptor.EnumDescriptor(
+ name='Context',
+ full_name='MumbleProto.ContextActionModify.Context',
+ filename=None,
+ file=DESCRIPTOR,
+ values=[
+ _descriptor.EnumValueDescriptor(
+ name='Server', index=0, number=1,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='Channel', index=1, number=2,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='User', index=2, number=4,
+ serialized_options=None,
+ type=None),
+ ],
+ containing_type=None,
+ serialized_options=None,
+ serialized_start=3315,
+ serialized_end=3359,
+)
+_sym_db.RegisterEnumDescriptor(_CONTEXTACTIONMODIFY_CONTEXT)
+
+_CONTEXTACTIONMODIFY_OPERATION = _descriptor.EnumDescriptor(
+ name='Operation',
+ full_name='MumbleProto.ContextActionModify.Operation',
+ filename=None,
+ file=DESCRIPTOR,
+ values=[
+ _descriptor.EnumValueDescriptor(
+ name='Add', index=0, number=0,
+ serialized_options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='Remove', index=1, number=1,
+ serialized_options=None,
+ type=None),
+ ],
+ containing_type=None,
+ serialized_options=None,
+ serialized_start=3361,
+ serialized_end=3393,
+)
+_sym_db.RegisterEnumDescriptor(_CONTEXTACTIONMODIFY_OPERATION)
+
+
+_VERSION = _descriptor.Descriptor(
+ name='Version',
+ full_name='MumbleProto.Version',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='version_v1', full_name='MumbleProto.Version.version_v1', index=0,
+ number=1, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='version_v2', full_name='MumbleProto.Version.version_v2', index=1,
+ number=5, type=4, cpp_type=4, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='release', full_name='MumbleProto.Version.release', index=2,
+ number=2, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='os', full_name='MumbleProto.Version.os', index=3,
+ number=3, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='os_version', full_name='MumbleProto.Version.os_version', index=4,
+ number=4, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=29,
+ serialized_end=127,
+)
+
+
+_UDPTUNNEL = _descriptor.Descriptor(
+ name='UDPTunnel',
+ full_name='MumbleProto.UDPTunnel',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='packet', full_name='MumbleProto.UDPTunnel.packet', index=0,
+ number=1, type=12, cpp_type=9, label=2,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=129,
+ serialized_end=156,
+)
+
+
+_AUTHENTICATE = _descriptor.Descriptor(
+ name='Authenticate',
+ full_name='MumbleProto.Authenticate',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='username', full_name='MumbleProto.Authenticate.username', index=0,
+ number=1, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='password', full_name='MumbleProto.Authenticate.password', index=1,
+ number=2, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='tokens', full_name='MumbleProto.Authenticate.tokens', index=2,
+ number=3, type=9, cpp_type=9, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='celt_versions', full_name='MumbleProto.Authenticate.celt_versions', index=3,
+ number=4, type=5, cpp_type=1, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='opus', full_name='MumbleProto.Authenticate.opus', index=4,
+ number=5, type=8, cpp_type=7, label=1,
+ has_default_value=True, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='client_type', full_name='MumbleProto.Authenticate.client_type', index=5,
+ number=6, type=5, cpp_type=1, label=1,
+ has_default_value=True, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=159,
+ serialized_end=293,
+)
+
+
+_PING = _descriptor.Descriptor(
+ name='Ping',
+ full_name='MumbleProto.Ping',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='timestamp', full_name='MumbleProto.Ping.timestamp', index=0,
+ number=1, type=4, cpp_type=4, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='good', full_name='MumbleProto.Ping.good', index=1,
+ number=2, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='late', full_name='MumbleProto.Ping.late', index=2,
+ number=3, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='lost', full_name='MumbleProto.Ping.lost', index=3,
+ number=4, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='resync', full_name='MumbleProto.Ping.resync', index=4,
+ number=5, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='udp_packets', full_name='MumbleProto.Ping.udp_packets', index=5,
+ number=6, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='tcp_packets', full_name='MumbleProto.Ping.tcp_packets', index=6,
+ number=7, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='udp_ping_avg', full_name='MumbleProto.Ping.udp_ping_avg', index=7,
+ number=8, type=2, cpp_type=6, label=1,
+ has_default_value=False, default_value=float(0),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='udp_ping_var', full_name='MumbleProto.Ping.udp_ping_var', index=8,
+ number=9, type=2, cpp_type=6, label=1,
+ has_default_value=False, default_value=float(0),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='tcp_ping_avg', full_name='MumbleProto.Ping.tcp_ping_avg', index=9,
+ number=10, type=2, cpp_type=6, label=1,
+ has_default_value=False, default_value=float(0),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='tcp_ping_var', full_name='MumbleProto.Ping.tcp_ping_var', index=10,
+ number=11, type=2, cpp_type=6, label=1,
+ has_default_value=False, default_value=float(0),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=296,
+ serialized_end=509,
+)
+
+
+_REJECT = _descriptor.Descriptor(
+ name='Reject',
+ full_name='MumbleProto.Reject',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='type', full_name='MumbleProto.Reject.type', index=0,
+ number=1, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='reason', full_name='MumbleProto.Reject.reason', index=1,
+ number=2, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ _REJECT_REJECTTYPE,
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=512,
+ serialized_end=759,
+)
+
+
+_SERVERSYNC = _descriptor.Descriptor(
+ name='ServerSync',
+ full_name='MumbleProto.ServerSync',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='session', full_name='MumbleProto.ServerSync.session', index=0,
+ number=1, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='max_bandwidth', full_name='MumbleProto.ServerSync.max_bandwidth', index=1,
+ number=2, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='welcome_text', full_name='MumbleProto.ServerSync.welcome_text', index=2,
+ number=3, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='permissions', full_name='MumbleProto.ServerSync.permissions', index=3,
+ number=4, type=4, cpp_type=4, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=761,
+ serialized_end=856,
+)
+
+
+_CHANNELREMOVE = _descriptor.Descriptor(
+ name='ChannelRemove',
+ full_name='MumbleProto.ChannelRemove',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='channel_id', full_name='MumbleProto.ChannelRemove.channel_id', index=0,
+ number=1, type=13, cpp_type=3, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=858,
+ serialized_end=893,
+)
+
+
+_CHANNELSTATE = _descriptor.Descriptor(
+ name='ChannelState',
+ full_name='MumbleProto.ChannelState',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='channel_id', full_name='MumbleProto.ChannelState.channel_id', index=0,
+ number=1, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='parent', full_name='MumbleProto.ChannelState.parent', index=1,
+ number=2, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='name', full_name='MumbleProto.ChannelState.name', index=2,
+ number=3, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='links', full_name='MumbleProto.ChannelState.links', index=3,
+ number=4, type=13, cpp_type=3, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='description', full_name='MumbleProto.ChannelState.description', index=4,
+ number=5, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='links_add', full_name='MumbleProto.ChannelState.links_add', index=5,
+ number=6, type=13, cpp_type=3, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='links_remove', full_name='MumbleProto.ChannelState.links_remove', index=6,
+ number=7, type=13, cpp_type=3, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='temporary', full_name='MumbleProto.ChannelState.temporary', index=7,
+ number=8, type=8, cpp_type=7, label=1,
+ has_default_value=True, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='position', full_name='MumbleProto.ChannelState.position', index=8,
+ number=9, type=5, cpp_type=1, label=1,
+ has_default_value=True, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='description_hash', full_name='MumbleProto.ChannelState.description_hash', index=9,
+ number=10, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='max_users', full_name='MumbleProto.ChannelState.max_users', index=10,
+ number=11, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='is_enter_restricted', full_name='MumbleProto.ChannelState.is_enter_restricted', index=11,
+ number=12, type=8, cpp_type=7, label=1,
+ has_default_value=False, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='can_enter', full_name='MumbleProto.ChannelState.can_enter', index=12,
+ number=13, type=8, cpp_type=7, label=1,
+ has_default_value=False, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=896,
+ serialized_end=1177,
+)
+
+
+_USERREMOVE = _descriptor.Descriptor(
+ name='UserRemove',
+ full_name='MumbleProto.UserRemove',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='session', full_name='MumbleProto.UserRemove.session', index=0,
+ number=1, type=13, cpp_type=3, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='actor', full_name='MumbleProto.UserRemove.actor', index=1,
+ number=2, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='reason', full_name='MumbleProto.UserRemove.reason', index=2,
+ number=3, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='ban', full_name='MumbleProto.UserRemove.ban', index=3,
+ number=4, type=8, cpp_type=7, label=1,
+ has_default_value=False, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=1179,
+ serialized_end=1252,
+)
+
+
+_USERSTATE_VOLUMEADJUSTMENT = _descriptor.Descriptor(
+ name='VolumeAdjustment',
+ full_name='MumbleProto.UserState.VolumeAdjustment',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='listening_channel', full_name='MumbleProto.UserState.VolumeAdjustment.listening_channel', index=0,
+ number=1, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='volume_adjustment', full_name='MumbleProto.UserState.VolumeAdjustment.volume_adjustment', index=1,
+ number=2, type=2, cpp_type=6, label=1,
+ has_default_value=False, default_value=float(0),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=1797,
+ serialized_end=1869,
+)
+
+_USERSTATE = _descriptor.Descriptor(
+ name='UserState',
+ full_name='MumbleProto.UserState',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='session', full_name='MumbleProto.UserState.session', index=0,
+ number=1, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='actor', full_name='MumbleProto.UserState.actor', index=1,
+ number=2, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='name', full_name='MumbleProto.UserState.name', index=2,
+ number=3, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='user_id', full_name='MumbleProto.UserState.user_id', index=3,
+ number=4, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='channel_id', full_name='MumbleProto.UserState.channel_id', index=4,
+ number=5, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='mute', full_name='MumbleProto.UserState.mute', index=5,
+ number=6, type=8, cpp_type=7, label=1,
+ has_default_value=False, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='deaf', full_name='MumbleProto.UserState.deaf', index=6,
+ number=7, type=8, cpp_type=7, label=1,
+ has_default_value=False, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='suppress', full_name='MumbleProto.UserState.suppress', index=7,
+ number=8, type=8, cpp_type=7, label=1,
+ has_default_value=False, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='self_mute', full_name='MumbleProto.UserState.self_mute', index=8,
+ number=9, type=8, cpp_type=7, label=1,
+ has_default_value=False, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='self_deaf', full_name='MumbleProto.UserState.self_deaf', index=9,
+ number=10, type=8, cpp_type=7, label=1,
+ has_default_value=False, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='texture', full_name='MumbleProto.UserState.texture', index=10,
+ number=11, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='plugin_context', full_name='MumbleProto.UserState.plugin_context', index=11,
+ number=12, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='plugin_identity', full_name='MumbleProto.UserState.plugin_identity', index=12,
+ number=13, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='comment', full_name='MumbleProto.UserState.comment', index=13,
+ number=14, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='hash', full_name='MumbleProto.UserState.hash', index=14,
+ number=15, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='comment_hash', full_name='MumbleProto.UserState.comment_hash', index=15,
+ number=16, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='texture_hash', full_name='MumbleProto.UserState.texture_hash', index=16,
+ number=17, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='priority_speaker', full_name='MumbleProto.UserState.priority_speaker', index=17,
+ number=18, type=8, cpp_type=7, label=1,
+ has_default_value=False, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='recording', full_name='MumbleProto.UserState.recording', index=18,
+ number=19, type=8, cpp_type=7, label=1,
+ has_default_value=False, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='temporary_access_tokens', full_name='MumbleProto.UserState.temporary_access_tokens', index=19,
+ number=20, type=9, cpp_type=9, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='listening_channel_add', full_name='MumbleProto.UserState.listening_channel_add', index=20,
+ number=21, type=13, cpp_type=3, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='listening_channel_remove', full_name='MumbleProto.UserState.listening_channel_remove', index=21,
+ number=22, type=13, cpp_type=3, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='listening_volume_adjustment', full_name='MumbleProto.UserState.listening_volume_adjustment', index=22,
+ number=23, type=11, cpp_type=10, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[_USERSTATE_VOLUMEADJUSTMENT, ],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=1255,
+ serialized_end=1869,
+)
+
+
+_BANLIST_BANENTRY = _descriptor.Descriptor(
+ name='BanEntry',
+ full_name='MumbleProto.BanList.BanEntry',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='address', full_name='MumbleProto.BanList.BanEntry.address', index=0,
+ number=1, type=12, cpp_type=9, label=2,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='mask', full_name='MumbleProto.BanList.BanEntry.mask', index=1,
+ number=2, type=13, cpp_type=3, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='name', full_name='MumbleProto.BanList.BanEntry.name', index=2,
+ number=3, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='hash', full_name='MumbleProto.BanList.BanEntry.hash', index=3,
+ number=4, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='reason', full_name='MumbleProto.BanList.BanEntry.reason', index=4,
+ number=5, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='start', full_name='MumbleProto.BanList.BanEntry.start', index=5,
+ number=6, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='duration', full_name='MumbleProto.BanList.BanEntry.duration', index=6,
+ number=7, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=1950,
+ serialized_end=2068,
+)
+
+_BANLIST = _descriptor.Descriptor(
+ name='BanList',
+ full_name='MumbleProto.BanList',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='bans', full_name='MumbleProto.BanList.bans', index=0,
+ number=1, type=11, cpp_type=10, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='query', full_name='MumbleProto.BanList.query', index=1,
+ number=2, type=8, cpp_type=7, label=1,
+ has_default_value=True, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[_BANLIST_BANENTRY, ],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=1872,
+ serialized_end=2068,
+)
+
+
+_TEXTMESSAGE = _descriptor.Descriptor(
+ name='TextMessage',
+ full_name='MumbleProto.TextMessage',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='actor', full_name='MumbleProto.TextMessage.actor', index=0,
+ number=1, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='session', full_name='MumbleProto.TextMessage.session', index=1,
+ number=2, type=13, cpp_type=3, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='channel_id', full_name='MumbleProto.TextMessage.channel_id', index=2,
+ number=3, type=13, cpp_type=3, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='tree_id', full_name='MumbleProto.TextMessage.tree_id', index=3,
+ number=4, type=13, cpp_type=3, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='message', full_name='MumbleProto.TextMessage.message', index=4,
+ number=5, type=9, cpp_type=9, label=2,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=2070,
+ serialized_end=2169,
+)
+
+
+_PERMISSIONDENIED = _descriptor.Descriptor(
+ name='PermissionDenied',
+ full_name='MumbleProto.PermissionDenied',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='permission', full_name='MumbleProto.PermissionDenied.permission', index=0,
+ number=1, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='channel_id', full_name='MumbleProto.PermissionDenied.channel_id', index=1,
+ number=2, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='session', full_name='MumbleProto.PermissionDenied.session', index=2,
+ number=3, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='reason', full_name='MumbleProto.PermissionDenied.reason', index=3,
+ number=4, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='type', full_name='MumbleProto.PermissionDenied.type', index=4,
+ number=5, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='name', full_name='MumbleProto.PermissionDenied.name', index=5,
+ number=6, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ _PERMISSIONDENIED_DENYTYPE,
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=2172,
+ serialized_end=2595,
+)
+
+
+_ACL_CHANGROUP = _descriptor.Descriptor(
+ name='ChanGroup',
+ full_name='MumbleProto.ACL.ChanGroup',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='name', full_name='MumbleProto.ACL.ChanGroup.name', index=0,
+ number=1, type=9, cpp_type=9, label=2,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='inherited', full_name='MumbleProto.ACL.ChanGroup.inherited', index=1,
+ number=2, type=8, cpp_type=7, label=1,
+ has_default_value=True, default_value=True,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='inherit', full_name='MumbleProto.ACL.ChanGroup.inherit', index=2,
+ number=3, type=8, cpp_type=7, label=1,
+ has_default_value=True, default_value=True,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='inheritable', full_name='MumbleProto.ACL.ChanGroup.inheritable', index=3,
+ number=4, type=8, cpp_type=7, label=1,
+ has_default_value=True, default_value=True,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='add', full_name='MumbleProto.ACL.ChanGroup.add', index=4,
+ number=5, type=13, cpp_type=3, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='remove', full_name='MumbleProto.ACL.ChanGroup.remove', index=5,
+ number=6, type=13, cpp_type=3, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='inherited_members', full_name='MumbleProto.ACL.ChanGroup.inherited_members', index=6,
+ number=7, type=13, cpp_type=3, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=2760,
+ serialized_end=2916,
+)
+
+_ACL_CHANACL = _descriptor.Descriptor(
+ name='ChanACL',
+ full_name='MumbleProto.ACL.ChanACL',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='apply_here', full_name='MumbleProto.ACL.ChanACL.apply_here', index=0,
+ number=1, type=8, cpp_type=7, label=1,
+ has_default_value=True, default_value=True,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='apply_subs', full_name='MumbleProto.ACL.ChanACL.apply_subs', index=1,
+ number=2, type=8, cpp_type=7, label=1,
+ has_default_value=True, default_value=True,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='inherited', full_name='MumbleProto.ACL.ChanACL.inherited', index=2,
+ number=3, type=8, cpp_type=7, label=1,
+ has_default_value=True, default_value=True,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='user_id', full_name='MumbleProto.ACL.ChanACL.user_id', index=3,
+ number=4, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='group', full_name='MumbleProto.ACL.ChanACL.group', index=4,
+ number=5, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='grant', full_name='MumbleProto.ACL.ChanACL.grant', index=5,
+ number=6, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='deny', full_name='MumbleProto.ACL.ChanACL.deny', index=6,
+ number=7, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=2919,
+ serialized_end=3066,
+)
+
+_ACL = _descriptor.Descriptor(
+ name='ACL',
+ full_name='MumbleProto.ACL',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='channel_id', full_name='MumbleProto.ACL.channel_id', index=0,
+ number=1, type=13, cpp_type=3, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='inherit_acls', full_name='MumbleProto.ACL.inherit_acls', index=1,
+ number=2, type=8, cpp_type=7, label=1,
+ has_default_value=True, default_value=True,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='groups', full_name='MumbleProto.ACL.groups', index=2,
+ number=3, type=11, cpp_type=10, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='acls', full_name='MumbleProto.ACL.acls', index=3,
+ number=4, type=11, cpp_type=10, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='query', full_name='MumbleProto.ACL.query', index=4,
+ number=5, type=8, cpp_type=7, label=1,
+ has_default_value=True, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[_ACL_CHANGROUP, _ACL_CHANACL, ],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=2598,
+ serialized_end=3066,
+)
+
+
+_QUERYUSERS = _descriptor.Descriptor(
+ name='QueryUsers',
+ full_name='MumbleProto.QueryUsers',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='ids', full_name='MumbleProto.QueryUsers.ids', index=0,
+ number=1, type=13, cpp_type=3, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='names', full_name='MumbleProto.QueryUsers.names', index=1,
+ number=2, type=9, cpp_type=9, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=3068,
+ serialized_end=3108,
+)
+
+
+_CRYPTSETUP = _descriptor.Descriptor(
+ name='CryptSetup',
+ full_name='MumbleProto.CryptSetup',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='key', full_name='MumbleProto.CryptSetup.key', index=0,
+ number=1, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='client_nonce', full_name='MumbleProto.CryptSetup.client_nonce', index=1,
+ number=2, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='server_nonce', full_name='MumbleProto.CryptSetup.server_nonce', index=2,
+ number=3, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=3110,
+ serialized_end=3179,
+)
+
+
+_CONTEXTACTIONMODIFY = _descriptor.Descriptor(
+ name='ContextActionModify',
+ full_name='MumbleProto.ContextActionModify',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='action', full_name='MumbleProto.ContextActionModify.action', index=0,
+ number=1, type=9, cpp_type=9, label=2,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='text', full_name='MumbleProto.ContextActionModify.text', index=1,
+ number=2, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='context', full_name='MumbleProto.ContextActionModify.context', index=2,
+ number=3, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='operation', full_name='MumbleProto.ContextActionModify.operation', index=3,
+ number=4, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ _CONTEXTACTIONMODIFY_CONTEXT,
+ _CONTEXTACTIONMODIFY_OPERATION,
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=3182,
+ serialized_end=3393,
+)
+
+
+_CONTEXTACTION = _descriptor.Descriptor(
+ name='ContextAction',
+ full_name='MumbleProto.ContextAction',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='session', full_name='MumbleProto.ContextAction.session', index=0,
+ number=1, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='channel_id', full_name='MumbleProto.ContextAction.channel_id', index=1,
+ number=2, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='action', full_name='MumbleProto.ContextAction.action', index=2,
+ number=3, type=9, cpp_type=9, label=2,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=3395,
+ serialized_end=3463,
+)
+
+
+_USERLIST_USER = _descriptor.Descriptor(
+ name='User',
+ full_name='MumbleProto.UserList.User',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='user_id', full_name='MumbleProto.UserList.User.user_id', index=0,
+ number=1, type=13, cpp_type=3, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='name', full_name='MumbleProto.UserList.User.name', index=1,
+ number=2, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='last_seen', full_name='MumbleProto.UserList.User.last_seen', index=2,
+ number=3, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='last_channel', full_name='MumbleProto.UserList.User.last_channel', index=3,
+ number=4, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=3521,
+ serialized_end=3599,
+)
+
+_USERLIST = _descriptor.Descriptor(
+ name='UserList',
+ full_name='MumbleProto.UserList',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='users', full_name='MumbleProto.UserList.users', index=0,
+ number=1, type=11, cpp_type=10, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[_USERLIST_USER, ],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=3466,
+ serialized_end=3599,
+)
+
+
+_VOICETARGET_TARGET = _descriptor.Descriptor(
+ name='Target',
+ full_name='MumbleProto.VoiceTarget.Target',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='session', full_name='MumbleProto.VoiceTarget.Target.session', index=0,
+ number=1, type=13, cpp_type=3, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='channel_id', full_name='MumbleProto.VoiceTarget.Target.channel_id', index=1,
+ number=2, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='group', full_name='MumbleProto.VoiceTarget.Target.group', index=2,
+ number=3, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='links', full_name='MumbleProto.VoiceTarget.Target.links', index=3,
+ number=4, type=8, cpp_type=7, label=1,
+ has_default_value=True, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='children', full_name='MumbleProto.VoiceTarget.Target.children', index=4,
+ number=5, type=8, cpp_type=7, label=1,
+ has_default_value=True, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=3679,
+ serialized_end=3786,
+)
+
+_VOICETARGET = _descriptor.Descriptor(
+ name='VoiceTarget',
+ full_name='MumbleProto.VoiceTarget',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='id', full_name='MumbleProto.VoiceTarget.id', index=0,
+ number=1, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='targets', full_name='MumbleProto.VoiceTarget.targets', index=1,
+ number=2, type=11, cpp_type=10, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[_VOICETARGET_TARGET, ],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=3602,
+ serialized_end=3786,
+)
+
+
+_PERMISSIONQUERY = _descriptor.Descriptor(
+ name='PermissionQuery',
+ full_name='MumbleProto.PermissionQuery',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='channel_id', full_name='MumbleProto.PermissionQuery.channel_id', index=0,
+ number=1, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='permissions', full_name='MumbleProto.PermissionQuery.permissions', index=1,
+ number=2, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='flush', full_name='MumbleProto.PermissionQuery.flush', index=2,
+ number=3, type=8, cpp_type=7, label=1,
+ has_default_value=True, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=3788,
+ serialized_end=3868,
+)
+
+
+_CODECVERSION = _descriptor.Descriptor(
+ name='CodecVersion',
+ full_name='MumbleProto.CodecVersion',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='alpha', full_name='MumbleProto.CodecVersion.alpha', index=0,
+ number=1, type=5, cpp_type=1, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='beta', full_name='MumbleProto.CodecVersion.beta', index=1,
+ number=2, type=5, cpp_type=1, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='prefer_alpha', full_name='MumbleProto.CodecVersion.prefer_alpha', index=2,
+ number=3, type=8, cpp_type=7, label=2,
+ has_default_value=True, default_value=True,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='opus', full_name='MumbleProto.CodecVersion.opus', index=3,
+ number=4, type=8, cpp_type=7, label=1,
+ has_default_value=True, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=3870,
+ serialized_end=3962,
+)
+
+
+_USERSTATS_STATS = _descriptor.Descriptor(
+ name='Stats',
+ full_name='MumbleProto.UserStats.Stats',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='good', full_name='MumbleProto.UserStats.Stats.good', index=0,
+ number=1, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='late', full_name='MumbleProto.UserStats.Stats.late', index=1,
+ number=2, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='lost', full_name='MumbleProto.UserStats.Stats.lost', index=2,
+ number=3, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='resync', full_name='MumbleProto.UserStats.Stats.resync', index=3,
+ number=4, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=4468,
+ serialized_end=4533,
+)
+
+_USERSTATS = _descriptor.Descriptor(
+ name='UserStats',
+ full_name='MumbleProto.UserStats',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='session', full_name='MumbleProto.UserStats.session', index=0,
+ number=1, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='stats_only', full_name='MumbleProto.UserStats.stats_only', index=1,
+ number=2, type=8, cpp_type=7, label=1,
+ has_default_value=True, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='certificates', full_name='MumbleProto.UserStats.certificates', index=2,
+ number=3, type=12, cpp_type=9, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='from_client', full_name='MumbleProto.UserStats.from_client', index=3,
+ number=4, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='from_server', full_name='MumbleProto.UserStats.from_server', index=4,
+ number=5, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='udp_packets', full_name='MumbleProto.UserStats.udp_packets', index=5,
+ number=6, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='tcp_packets', full_name='MumbleProto.UserStats.tcp_packets', index=6,
+ number=7, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='udp_ping_avg', full_name='MumbleProto.UserStats.udp_ping_avg', index=7,
+ number=8, type=2, cpp_type=6, label=1,
+ has_default_value=False, default_value=float(0),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='udp_ping_var', full_name='MumbleProto.UserStats.udp_ping_var', index=8,
+ number=9, type=2, cpp_type=6, label=1,
+ has_default_value=False, default_value=float(0),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='tcp_ping_avg', full_name='MumbleProto.UserStats.tcp_ping_avg', index=9,
+ number=10, type=2, cpp_type=6, label=1,
+ has_default_value=False, default_value=float(0),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='tcp_ping_var', full_name='MumbleProto.UserStats.tcp_ping_var', index=10,
+ number=11, type=2, cpp_type=6, label=1,
+ has_default_value=False, default_value=float(0),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='version', full_name='MumbleProto.UserStats.version', index=11,
+ number=12, type=11, cpp_type=10, label=1,
+ has_default_value=False, default_value=None,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='celt_versions', full_name='MumbleProto.UserStats.celt_versions', index=12,
+ number=13, type=5, cpp_type=1, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='address', full_name='MumbleProto.UserStats.address', index=13,
+ number=14, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='bandwidth', full_name='MumbleProto.UserStats.bandwidth', index=14,
+ number=15, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='onlinesecs', full_name='MumbleProto.UserStats.onlinesecs', index=15,
+ number=16, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='idlesecs', full_name='MumbleProto.UserStats.idlesecs', index=16,
+ number=17, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='strong_certificate', full_name='MumbleProto.UserStats.strong_certificate', index=17,
+ number=18, type=8, cpp_type=7, label=1,
+ has_default_value=True, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='opus', full_name='MumbleProto.UserStats.opus', index=18,
+ number=19, type=8, cpp_type=7, label=1,
+ has_default_value=True, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[_USERSTATS_STATS, ],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=3965,
+ serialized_end=4533,
+)
+
+
+_REQUESTBLOB = _descriptor.Descriptor(
+ name='RequestBlob',
+ full_name='MumbleProto.RequestBlob',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='session_texture', full_name='MumbleProto.RequestBlob.session_texture', index=0,
+ number=1, type=13, cpp_type=3, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='session_comment', full_name='MumbleProto.RequestBlob.session_comment', index=1,
+ number=2, type=13, cpp_type=3, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='channel_description', full_name='MumbleProto.RequestBlob.channel_description', index=2,
+ number=3, type=13, cpp_type=3, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=4535,
+ serialized_end=4627,
+)
+
+
+_SERVERCONFIG = _descriptor.Descriptor(
+ name='ServerConfig',
+ full_name='MumbleProto.ServerConfig',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='max_bandwidth', full_name='MumbleProto.ServerConfig.max_bandwidth', index=0,
+ number=1, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='welcome_text', full_name='MumbleProto.ServerConfig.welcome_text', index=1,
+ number=2, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='allow_html', full_name='MumbleProto.ServerConfig.allow_html', index=2,
+ number=3, type=8, cpp_type=7, label=1,
+ has_default_value=False, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='message_length', full_name='MumbleProto.ServerConfig.message_length', index=3,
+ number=4, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='image_message_length', full_name='MumbleProto.ServerConfig.image_message_length', index=4,
+ number=5, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='max_users', full_name='MumbleProto.ServerConfig.max_users', index=5,
+ number=6, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='recording_allowed', full_name='MumbleProto.ServerConfig.recording_allowed', index=6,
+ number=7, type=8, cpp_type=7, label=1,
+ has_default_value=False, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=4630,
+ serialized_end=4809,
+)
+
+
+_SUGGESTCONFIG = _descriptor.Descriptor(
+ name='SuggestConfig',
+ full_name='MumbleProto.SuggestConfig',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='version_v1', full_name='MumbleProto.SuggestConfig.version_v1', index=0,
+ number=1, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='version_v2', full_name='MumbleProto.SuggestConfig.version_v2', index=1,
+ number=4, type=4, cpp_type=4, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='positional', full_name='MumbleProto.SuggestConfig.positional', index=2,
+ number=2, type=8, cpp_type=7, label=1,
+ has_default_value=False, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='push_to_talk', full_name='MumbleProto.SuggestConfig.push_to_talk', index=3,
+ number=3, type=8, cpp_type=7, label=1,
+ has_default_value=False, default_value=False,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=4811,
+ serialized_end=4908,
+)
+
+
+_PLUGINDATATRANSMISSION = _descriptor.Descriptor(
+ name='PluginDataTransmission',
+ full_name='MumbleProto.PluginDataTransmission',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='senderSession', full_name='MumbleProto.PluginDataTransmission.senderSession', index=0,
+ number=1, type=13, cpp_type=3, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='receiverSessions', full_name='MumbleProto.PluginDataTransmission.receiverSessions', index=1,
+ number=2, type=13, cpp_type=3, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=_b('\020\001'), file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='data', full_name='MumbleProto.PluginDataTransmission.data', index=2,
+ number=3, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b(""),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ _descriptor.FieldDescriptor(
+ name='dataID', full_name='MumbleProto.PluginDataTransmission.dataID', index=3,
+ number=4, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=_b("").decode('utf-8'),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ serialized_options=None, file=DESCRIPTOR),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax='proto2',
+ extension_ranges=[],
+ oneofs=[
+ ],
+ serialized_start=4910,
+ serialized_end=5017,
+)
+
+_REJECT.fields_by_name['type'].enum_type = _REJECT_REJECTTYPE
+_REJECT_REJECTTYPE.containing_type = _REJECT
+_USERSTATE_VOLUMEADJUSTMENT.containing_type = _USERSTATE
+_USERSTATE.fields_by_name['listening_volume_adjustment'].message_type = _USERSTATE_VOLUMEADJUSTMENT
+_BANLIST_BANENTRY.containing_type = _BANLIST
+_BANLIST.fields_by_name['bans'].message_type = _BANLIST_BANENTRY
+_PERMISSIONDENIED.fields_by_name['type'].enum_type = _PERMISSIONDENIED_DENYTYPE
+_PERMISSIONDENIED_DENYTYPE.containing_type = _PERMISSIONDENIED
+_ACL_CHANGROUP.containing_type = _ACL
+_ACL_CHANACL.containing_type = _ACL
+_ACL.fields_by_name['groups'].message_type = _ACL_CHANGROUP
+_ACL.fields_by_name['acls'].message_type = _ACL_CHANACL
+_CONTEXTACTIONMODIFY.fields_by_name['operation'].enum_type = _CONTEXTACTIONMODIFY_OPERATION
+_CONTEXTACTIONMODIFY_CONTEXT.containing_type = _CONTEXTACTIONMODIFY
+_CONTEXTACTIONMODIFY_OPERATION.containing_type = _CONTEXTACTIONMODIFY
+_USERLIST_USER.containing_type = _USERLIST
+_USERLIST.fields_by_name['users'].message_type = _USERLIST_USER
+_VOICETARGET_TARGET.containing_type = _VOICETARGET
+_VOICETARGET.fields_by_name['targets'].message_type = _VOICETARGET_TARGET
+_USERSTATS_STATS.containing_type = _USERSTATS
+_USERSTATS.fields_by_name['from_client'].message_type = _USERSTATS_STATS
+_USERSTATS.fields_by_name['from_server'].message_type = _USERSTATS_STATS
+_USERSTATS.fields_by_name['version'].message_type = _VERSION
+DESCRIPTOR.message_types_by_name['Version'] = _VERSION
+DESCRIPTOR.message_types_by_name['UDPTunnel'] = _UDPTUNNEL
+DESCRIPTOR.message_types_by_name['Authenticate'] = _AUTHENTICATE
+DESCRIPTOR.message_types_by_name['Ping'] = _PING
+DESCRIPTOR.message_types_by_name['Reject'] = _REJECT
+DESCRIPTOR.message_types_by_name['ServerSync'] = _SERVERSYNC
+DESCRIPTOR.message_types_by_name['ChannelRemove'] = _CHANNELREMOVE
+DESCRIPTOR.message_types_by_name['ChannelState'] = _CHANNELSTATE
+DESCRIPTOR.message_types_by_name['UserRemove'] = _USERREMOVE
+DESCRIPTOR.message_types_by_name['UserState'] = _USERSTATE
+DESCRIPTOR.message_types_by_name['BanList'] = _BANLIST
+DESCRIPTOR.message_types_by_name['TextMessage'] = _TEXTMESSAGE
+DESCRIPTOR.message_types_by_name['PermissionDenied'] = _PERMISSIONDENIED
+DESCRIPTOR.message_types_by_name['ACL'] = _ACL
+DESCRIPTOR.message_types_by_name['QueryUsers'] = _QUERYUSERS
+DESCRIPTOR.message_types_by_name['CryptSetup'] = _CRYPTSETUP
+DESCRIPTOR.message_types_by_name['ContextActionModify'] = _CONTEXTACTIONMODIFY
+DESCRIPTOR.message_types_by_name['ContextAction'] = _CONTEXTACTION
+DESCRIPTOR.message_types_by_name['UserList'] = _USERLIST
+DESCRIPTOR.message_types_by_name['VoiceTarget'] = _VOICETARGET
+DESCRIPTOR.message_types_by_name['PermissionQuery'] = _PERMISSIONQUERY
+DESCRIPTOR.message_types_by_name['CodecVersion'] = _CODECVERSION
+DESCRIPTOR.message_types_by_name['UserStats'] = _USERSTATS
+DESCRIPTOR.message_types_by_name['RequestBlob'] = _REQUESTBLOB
+DESCRIPTOR.message_types_by_name['ServerConfig'] = _SERVERCONFIG
+DESCRIPTOR.message_types_by_name['SuggestConfig'] = _SUGGESTCONFIG
+DESCRIPTOR.message_types_by_name['PluginDataTransmission'] = _PLUGINDATATRANSMISSION
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+Version = _reflection.GeneratedProtocolMessageType('Version', (_message.Message,), dict(
+ DESCRIPTOR = _VERSION,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.Version)
+ ))
+_sym_db.RegisterMessage(Version)
+
+UDPTunnel = _reflection.GeneratedProtocolMessageType('UDPTunnel', (_message.Message,), dict(
+ DESCRIPTOR = _UDPTUNNEL,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.UDPTunnel)
+ ))
+_sym_db.RegisterMessage(UDPTunnel)
+
+Authenticate = _reflection.GeneratedProtocolMessageType('Authenticate', (_message.Message,), dict(
+ DESCRIPTOR = _AUTHENTICATE,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.Authenticate)
+ ))
+_sym_db.RegisterMessage(Authenticate)
+
+Ping = _reflection.GeneratedProtocolMessageType('Ping', (_message.Message,), dict(
+ DESCRIPTOR = _PING,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.Ping)
+ ))
+_sym_db.RegisterMessage(Ping)
+
+Reject = _reflection.GeneratedProtocolMessageType('Reject', (_message.Message,), dict(
+ DESCRIPTOR = _REJECT,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.Reject)
+ ))
+_sym_db.RegisterMessage(Reject)
+
+ServerSync = _reflection.GeneratedProtocolMessageType('ServerSync', (_message.Message,), dict(
+ DESCRIPTOR = _SERVERSYNC,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.ServerSync)
+ ))
+_sym_db.RegisterMessage(ServerSync)
+
+ChannelRemove = _reflection.GeneratedProtocolMessageType('ChannelRemove', (_message.Message,), dict(
+ DESCRIPTOR = _CHANNELREMOVE,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.ChannelRemove)
+ ))
+_sym_db.RegisterMessage(ChannelRemove)
+
+ChannelState = _reflection.GeneratedProtocolMessageType('ChannelState', (_message.Message,), dict(
+ DESCRIPTOR = _CHANNELSTATE,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.ChannelState)
+ ))
+_sym_db.RegisterMessage(ChannelState)
+
+UserRemove = _reflection.GeneratedProtocolMessageType('UserRemove', (_message.Message,), dict(
+ DESCRIPTOR = _USERREMOVE,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.UserRemove)
+ ))
+_sym_db.RegisterMessage(UserRemove)
+
+UserState = _reflection.GeneratedProtocolMessageType('UserState', (_message.Message,), dict(
+
+ VolumeAdjustment = _reflection.GeneratedProtocolMessageType('VolumeAdjustment', (_message.Message,), dict(
+ DESCRIPTOR = _USERSTATE_VOLUMEADJUSTMENT,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.UserState.VolumeAdjustment)
+ ))
+ ,
+ DESCRIPTOR = _USERSTATE,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.UserState)
+ ))
+_sym_db.RegisterMessage(UserState)
+_sym_db.RegisterMessage(UserState.VolumeAdjustment)
+
+BanList = _reflection.GeneratedProtocolMessageType('BanList', (_message.Message,), dict(
+
+ BanEntry = _reflection.GeneratedProtocolMessageType('BanEntry', (_message.Message,), dict(
+ DESCRIPTOR = _BANLIST_BANENTRY,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.BanList.BanEntry)
+ ))
+ ,
+ DESCRIPTOR = _BANLIST,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.BanList)
+ ))
+_sym_db.RegisterMessage(BanList)
+_sym_db.RegisterMessage(BanList.BanEntry)
+
+TextMessage = _reflection.GeneratedProtocolMessageType('TextMessage', (_message.Message,), dict(
+ DESCRIPTOR = _TEXTMESSAGE,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.TextMessage)
+ ))
+_sym_db.RegisterMessage(TextMessage)
+
+PermissionDenied = _reflection.GeneratedProtocolMessageType('PermissionDenied', (_message.Message,), dict(
+ DESCRIPTOR = _PERMISSIONDENIED,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.PermissionDenied)
+ ))
+_sym_db.RegisterMessage(PermissionDenied)
+
+ACL = _reflection.GeneratedProtocolMessageType('ACL', (_message.Message,), dict(
+
+ ChanGroup = _reflection.GeneratedProtocolMessageType('ChanGroup', (_message.Message,), dict(
+ DESCRIPTOR = _ACL_CHANGROUP,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.ACL.ChanGroup)
+ ))
+ ,
+
+ ChanACL = _reflection.GeneratedProtocolMessageType('ChanACL', (_message.Message,), dict(
+ DESCRIPTOR = _ACL_CHANACL,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.ACL.ChanACL)
+ ))
+ ,
+ DESCRIPTOR = _ACL,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.ACL)
+ ))
+_sym_db.RegisterMessage(ACL)
+_sym_db.RegisterMessage(ACL.ChanGroup)
+_sym_db.RegisterMessage(ACL.ChanACL)
+
+QueryUsers = _reflection.GeneratedProtocolMessageType('QueryUsers', (_message.Message,), dict(
+ DESCRIPTOR = _QUERYUSERS,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.QueryUsers)
+ ))
+_sym_db.RegisterMessage(QueryUsers)
+
+CryptSetup = _reflection.GeneratedProtocolMessageType('CryptSetup', (_message.Message,), dict(
+ DESCRIPTOR = _CRYPTSETUP,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.CryptSetup)
+ ))
+_sym_db.RegisterMessage(CryptSetup)
+
+ContextActionModify = _reflection.GeneratedProtocolMessageType('ContextActionModify', (_message.Message,), dict(
+ DESCRIPTOR = _CONTEXTACTIONMODIFY,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.ContextActionModify)
+ ))
+_sym_db.RegisterMessage(ContextActionModify)
+
+ContextAction = _reflection.GeneratedProtocolMessageType('ContextAction', (_message.Message,), dict(
+ DESCRIPTOR = _CONTEXTACTION,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.ContextAction)
+ ))
+_sym_db.RegisterMessage(ContextAction)
+
+UserList = _reflection.GeneratedProtocolMessageType('UserList', (_message.Message,), dict(
+
+ User = _reflection.GeneratedProtocolMessageType('User', (_message.Message,), dict(
+ DESCRIPTOR = _USERLIST_USER,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.UserList.User)
+ ))
+ ,
+ DESCRIPTOR = _USERLIST,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.UserList)
+ ))
+_sym_db.RegisterMessage(UserList)
+_sym_db.RegisterMessage(UserList.User)
+
+VoiceTarget = _reflection.GeneratedProtocolMessageType('VoiceTarget', (_message.Message,), dict(
+
+ Target = _reflection.GeneratedProtocolMessageType('Target', (_message.Message,), dict(
+ DESCRIPTOR = _VOICETARGET_TARGET,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.VoiceTarget.Target)
+ ))
+ ,
+ DESCRIPTOR = _VOICETARGET,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.VoiceTarget)
+ ))
+_sym_db.RegisterMessage(VoiceTarget)
+_sym_db.RegisterMessage(VoiceTarget.Target)
+
+PermissionQuery = _reflection.GeneratedProtocolMessageType('PermissionQuery', (_message.Message,), dict(
+ DESCRIPTOR = _PERMISSIONQUERY,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.PermissionQuery)
+ ))
+_sym_db.RegisterMessage(PermissionQuery)
+
+CodecVersion = _reflection.GeneratedProtocolMessageType('CodecVersion', (_message.Message,), dict(
+ DESCRIPTOR = _CODECVERSION,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.CodecVersion)
+ ))
+_sym_db.RegisterMessage(CodecVersion)
+
+UserStats = _reflection.GeneratedProtocolMessageType('UserStats', (_message.Message,), dict(
+
+ Stats = _reflection.GeneratedProtocolMessageType('Stats', (_message.Message,), dict(
+ DESCRIPTOR = _USERSTATS_STATS,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.UserStats.Stats)
+ ))
+ ,
+ DESCRIPTOR = _USERSTATS,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.UserStats)
+ ))
+_sym_db.RegisterMessage(UserStats)
+_sym_db.RegisterMessage(UserStats.Stats)
+
+RequestBlob = _reflection.GeneratedProtocolMessageType('RequestBlob', (_message.Message,), dict(
+ DESCRIPTOR = _REQUESTBLOB,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.RequestBlob)
+ ))
+_sym_db.RegisterMessage(RequestBlob)
+
+ServerConfig = _reflection.GeneratedProtocolMessageType('ServerConfig', (_message.Message,), dict(
+ DESCRIPTOR = _SERVERCONFIG,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.ServerConfig)
+ ))
+_sym_db.RegisterMessage(ServerConfig)
+
+SuggestConfig = _reflection.GeneratedProtocolMessageType('SuggestConfig', (_message.Message,), dict(
+ DESCRIPTOR = _SUGGESTCONFIG,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.SuggestConfig)
+ ))
+_sym_db.RegisterMessage(SuggestConfig)
+
+PluginDataTransmission = _reflection.GeneratedProtocolMessageType('PluginDataTransmission', (_message.Message,), dict(
+ DESCRIPTOR = _PLUGINDATATRANSMISSION,
+ __module__ = 'Mumble_pb2'
+ # @@protoc_insertion_point(class_scope:MumbleProto.PluginDataTransmission)
+ ))
+_sym_db.RegisterMessage(PluginDataTransmission)
+
+
+DESCRIPTOR._options = None
+_PLUGINDATATRANSMISSION.fields_by_name['receiverSessions']._options = None
+# @@protoc_insertion_point(module_scope)
diff --git a/pymumble_py3/soundoutput.py b/pymumble_py3/soundoutput.py
new file mode 100644
index 0000000..678a71d
--- /dev/null
+++ b/pymumble_py3/soundoutput.py
@@ -0,0 +1,215 @@
+# -*- coding: utf-8 -*-
+
+from time import time
+import struct
+import threading
+import socket
+import opuslib
+
+from .constants import *
+from .errors import CodecNotSupportedError
+from .tools import VarInt
+from .messages import VoiceTarget
+
+
+class SoundOutput:
+ """
+ Class managing the sounds that must be sent to the server (best sent in a multiple of audio_per_packet samples)
+ The buffering is the responsibility of the caller, any partial sound will be sent without delay
+ """
+
+ def __init__(self, mumble_object, audio_per_packet, bandwidth, stereo=False, opus_profile=PYMUMBLE_AUDIO_TYPE_OPUS_PROFILE):
+ """
+ audio_per_packet=packet audio duration in sec
+ bandwidth=maximum total outgoing bandwidth
+ """
+ self.mumble_object = mumble_object
+
+ self.Log = self.mumble_object.Log
+
+ self.pcm = []
+ self.lock = threading.Lock()
+
+ self.codec = None # codec currently requested by the server
+ self.encoder = None # codec instance currently used to encode
+ self.encoder_framesize = None # size of an audio frame for the current codec (OPUS=audio_per_packet, CELT=0.01s)
+ self.opus_profile = opus_profile
+ self.channels = 1 if not stereo else 2
+
+ self.set_audio_per_packet(audio_per_packet)
+ self.set_bandwidth(bandwidth)
+
+ self.codec_type = None # codec type number to be used in audio packets
+ self.target = 0 # target is not implemented yet, so always 0
+
+ self.sequence_start_time = 0 # time of sequence 1
+ self.sequence_last_time = 0 # time of the last emitted packet
+ self.sequence = 0 # current sequence
+
+ def send_audio(self):
+ """send the available audio to the server, taking care of the timing"""
+ if not self.encoder or len(self.pcm) == 0: # no codec configured or no audio sent
+ return ()
+
+ samples = int(self.encoder_framesize * PYMUMBLE_SAMPLERATE * 2 * self.channels) # number of samples in an encoder frame
+
+ while len(self.pcm) > 0 and self.sequence_last_time + self.audio_per_packet <= time(): # audio to send and time to send it (since last packet)
+ current_time = time()
+ if self.sequence_last_time + PYMUMBLE_SEQUENCE_RESET_INTERVAL <= current_time: # waited enough, resetting sequence to 0
+ self.sequence = 0
+ self.sequence_start_time = current_time
+ self.sequence_last_time = current_time
+ elif self.sequence_last_time + (self.audio_per_packet * 2) <= current_time: # give some slack (2*audio_per_frame) before interrupting a continuous sequence
+ # calculating sequence after a pause
+ self.sequence = int((current_time - self.sequence_start_time) / PYMUMBLE_SEQUENCE_DURATION)
+ self.sequence_last_time = self.sequence_start_time + (self.sequence * PYMUMBLE_SEQUENCE_DURATION)
+ else: # continuous sound
+ self.sequence += int(self.audio_per_packet / PYMUMBLE_SEQUENCE_DURATION)
+ self.sequence_last_time = self.sequence_start_time + (self.sequence * PYMUMBLE_SEQUENCE_DURATION)
+
+ payload = bytearray() # content of the whole packet, without tcptunnel header
+ audio_encoded = 0 # audio time already in the packet
+
+ while len(self.pcm) > 0 and audio_encoded < self.audio_per_packet: # more audio to be sent and packet not full
+ self.lock.acquire()
+ to_encode = self.pcm.pop(0)
+ self.lock.release()
+
+ if len(to_encode) != samples: # pad to_encode if needed to match sample length
+ to_encode += b'\x00' * (samples - len(to_encode))
+
+ try:
+ encoded = self.encoder.encode(to_encode, len(to_encode) // (2 * self.channels))
+ except opuslib.exceptions.OpusError:
+ encoded = b''
+
+ audio_encoded += self.encoder_framesize
+
+ # create the audio frame header
+ if self.codec_type == PYMUMBLE_AUDIO_TYPE_OPUS:
+ frameheader = VarInt(len(encoded)).encode()
+ else:
+ frameheader = len(encoded)
+ if audio_encoded < self.audio_per_packet and len(self.pcm) > 0: # if not last frame for the packet, set the terminator bit
+ frameheader += (1 << 7)
+ frameheader = struct.pack('!B', frameheader)
+
+ payload += frameheader + encoded # add the frame to the packet
+
+ header = self.codec_type << 5 # encapsulate in audio packet
+ sequence = VarInt(self.sequence).encode()
+
+ udppacket = struct.pack('!B', header | self.target) + sequence + payload
+ if self.mumble_object.positional:
+ udppacket += struct.pack("fff", self.mumble_object.positional[0], self.mumble_object.positional[1], self.mumble_object.positional[2])
+
+ self.Log.debug("audio packet to send: sequence:{sequence}, type:{type}, length:{len}".format(
+ sequence=self.sequence,
+ type=self.codec_type,
+ len=len(udppacket)
+ ))
+
+ tcppacket = struct.pack("!HL", PYMUMBLE_MSG_TYPES_UDPTUNNEL, len(udppacket)) + udppacket # encapsulate in tcp tunnel
+
+ while len(tcppacket) > 0:
+ sent = self.mumble_object.control_socket.send(tcppacket)
+ if sent < 0:
+ raise socket.error("Server socket error")
+ tcppacket = tcppacket[sent:]
+
+ def get_audio_per_packet(self):
+ """return the configured length of a audio packet (in ms)"""
+ return self.audio_per_packet
+
+ def set_audio_per_packet(self, audio_per_packet):
+ """set the length of an audio packet (in ms)"""
+ self.audio_per_packet = audio_per_packet
+ self.create_encoder()
+
+ def get_bandwidth(self):
+ """get the configured bandwidth for the audio output"""
+ return self.bandwidth
+
+ def set_bandwidth(self, bandwidth):
+ """set the bandwidth for the audio output"""
+ self.bandwidth = bandwidth
+ self._set_bandwidth()
+
+ def _set_bandwidth(self):
+ """do the calculation of the overhead and configure the actual bitrate for the codec"""
+ if self.encoder:
+ overhead_per_packet = 20 # IP header in bytes
+ overhead_per_packet += (3 * int(self.audio_per_packet / self.encoder_framesize)) # overhead per frame
+ if self.mumble_object.udp_active:
+ overhead_per_packet += 12 # UDP header
+ else:
+ overhead_per_packet += 20 # TCP header
+ overhead_per_packet += 6 # TCPTunnel encapsulation
+
+ overhead_per_second = int(overhead_per_packet * 8 / self.audio_per_packet) # in bits
+
+ self.Log.debug(
+ "Bandwidth is {bandwidth}, downgrading to {bitrate} due to the protocol overhead".format(bandwidth=self.bandwidth, bitrate=self.bandwidth - overhead_per_second))
+
+ self.encoder.bitrate = self.bandwidth - overhead_per_second
+
+ def add_sound(self, pcm):
+ """add sound to be sent (in PCM 16 bits signed format)"""
+ if len(pcm) % 2 != 0: # check that the data is align on 16 bits
+ raise Exception("pcm data must be 16 bits")
+
+ samples = int(self.encoder_framesize * PYMUMBLE_SAMPLERATE * 2 * self.channels) # number of samples in an encoder frame
+
+ self.lock.acquire()
+ if len(self.pcm) and len(self.pcm[-1]) < samples:
+ initial_offset = samples - len(self.pcm[-1])
+ self.pcm[-1] += pcm[:initial_offset]
+ else:
+ initial_offset = 0
+ for i in range(initial_offset, len(pcm), samples):
+ self.pcm.append(pcm[i:i + samples])
+ self.lock.release()
+
+ def clear_buffer(self):
+ self.lock.acquire()
+ self.pcm = []
+ self.lock.release()
+
+ def get_buffer_size(self):
+ """return the size of the unsent buffer in sec"""
+ return sum(len(chunk) for chunk in self.pcm) / 2. / PYMUMBLE_SAMPLERATE / self.channels
+
+ def set_default_codec(self, codecversion):
+ """Set the default codec to be used to send packets"""
+ self.codec = codecversion
+ self.create_encoder()
+
+ def create_encoder(self):
+ """create the encoder instance, and set related constants"""
+ if not self.codec:
+ return ()
+
+ if self.codec.opus:
+ self.encoder = opuslib.Encoder(PYMUMBLE_SAMPLERATE, self.channels, self.opus_profile)
+ self.encoder_framesize = self.audio_per_packet
+ self.codec_type = PYMUMBLE_AUDIO_TYPE_OPUS
+ else:
+ raise CodecNotSupportedError('')
+
+ self._set_bandwidth()
+
+ def set_whisper(self, target_id, channel=False):
+ if not target_id:
+ return
+ if type(target_id) is int:
+ target_id = [target_id]
+ self.target = 2
+ if channel:
+ self.target = 1
+ cmd = VoiceTarget(self.target, target_id)
+ self.mumble_object.execute_command(cmd)
+
+ def remove_whisper(self):
+ self.target = 0
+ cmd = VoiceTarget(self.target, [])
+ self.mumble_object.execute_command(cmd)
diff --git a/pymumble_py3/soundqueue.py b/pymumble_py3/soundqueue.py
new file mode 100644
index 0000000..be00bc5
--- /dev/null
+++ b/pymumble_py3/soundqueue.py
@@ -0,0 +1,142 @@
+# -*- coding: utf-8 -*-
+import time
+from threading import Lock
+from collections import deque
+
+import opuslib
+
+from .constants import *
+
+
+class SoundQueue:
+ """
+ Per user storage of received audio frames
+ Takes care of the decoding of the received audio
+ """
+ def __init__(self, mumble_object):
+ self.mumble_object = mumble_object
+
+ self.queue = deque()
+ self.start_sequence = None
+ self.start_time = None
+
+ self.receive_sound = True
+
+ self.lock = Lock()
+
+ # to be sure, create every supported decoders for all users
+ # sometime, clients still use a codec for a while after server request another...
+ self.decoders = {
+ PYMUMBLE_AUDIO_TYPE_OPUS: opuslib.Decoder(PYMUMBLE_SAMPLERATE, 1)
+ }
+
+ def set_receive_sound(self, value):
+ """Define if received sounds must be kept or discarded in this specific queue (user)"""
+ if value:
+ self.receive_sound = True
+ else:
+ self.receive_sound = False
+
+ def add(self, audio, sequence, type, target):
+ """Add a new audio frame to the queue, after decoding"""
+ if not self.receive_sound:
+ return None
+
+ self.lock.acquire()
+
+ try:
+ pcm = self.decoders[type].decode(audio, PYMUMBLE_READ_BUFFER_SIZE)
+
+ if not self.start_sequence or sequence <= self.start_sequence:
+ # New sequence started
+ self.start_time = time.time()
+ self.start_sequence = sequence
+ calculated_time = self.start_time
+ else:
+ # calculating position in current sequence
+ calculated_time = self.start_time + (sequence - self.start_sequence) * PYMUMBLE_SEQUENCE_DURATION
+
+ newsound = SoundChunk(pcm, sequence, len(pcm), calculated_time, type, target)
+
+ if not self.mumble_object.callbacks.get_callback(PYMUMBLE_CLBK_SOUNDRECEIVED):
+ self.queue.appendleft(newsound)
+
+ if len(self.queue) > 1 and self.queue[0].time < self.queue[1].time:
+ # sort the audio chunk if it came out of order
+ cpt = 0
+ while cpt < len(self.queue) - 1 and self.queue[cpt].time < self.queue[cpt+1].time:
+ tmp = self.queue[cpt+1]
+ self.queue[cpt+1] = self.queue[cpt]
+ self.queue[cpt] = tmp
+
+ self.lock.release()
+ return newsound
+ except KeyError:
+ self.lock.release()
+ self.mumble_object.Log.error("Codec not supported (audio packet type {0})".format(type))
+ except Exception as e:
+ self.lock.release()
+ self.mumble_object.Log.error("error while decoding audio. sequence:{seq}, type:{type}. {error}".format(seq=sequence, type=type, error=str(e)))
+
+ def is_sound(self):
+ """Boolean to check if there is a sound frame in the queue"""
+ if len(self.queue) > 0:
+ return True
+ else:
+ return False
+
+ def get_sound(self, duration=None):
+ """Return the first sound of the queue and discard it"""
+ self.lock.acquire()
+
+ if len(self.queue) > 0:
+ if duration is None or self.first_sound().duration <= duration:
+ result = self.queue.pop()
+ else:
+ result = self.first_sound().extract_sound(duration)
+ else:
+ result = None
+
+ self.lock.release()
+ return result
+
+ def first_sound(self):
+ """Return the first sound of the queue, but keep it"""
+ if len(self.queue) > 0:
+ return self.queue[-1]
+ else:
+ return None
+
+
+class SoundChunk:
+ """
+ Object that contains the actual audio frame, in PCM format"""
+ def __init__(self, pcm, sequence, size, calculated_time, type, target, timestamp=time.time()):
+ self.timestamp = timestamp # measured time of arrival of the sound
+ self.time = calculated_time # calculated time of arrival of the sound (based on sequence)
+ self.pcm = pcm # audio data
+ self.sequence = sequence # sequence of the packet
+ self.size = size # size
+ self.duration = float(size) / 2 / PYMUMBLE_SAMPLERATE # duration in sec
+ self.type = type # type of the audio (codec)
+ self.target = target # target of the audio
+
+ def extract_sound(self, duration):
+ """Extract part of the chunk, leaving a valid chunk for the remaining part"""
+ size = int(duration*2*PYMUMBLE_SAMPLERATE)
+ result = SoundChunk(
+ self.pcm[:size],
+ self.sequence,
+ size,
+ self.time,
+ self.type,
+ self.target,
+ self.timestamp
+ )
+
+ self.pcm = self.pcm[size:]
+ self.duration -= duration
+ self.time += duration
+ self.size -= size
+
+ return result
diff --git a/pymumble_py3/test_crypto.py b/pymumble_py3/test_crypto.py
new file mode 100644
index 0000000..1cd3d3d
--- /dev/null
+++ b/pymumble_py3/test_crypto.py
@@ -0,0 +1,191 @@
+'''
+Ported from Mumble src/tests/TestCrypt/TestCrypt.cpp
+
+=============================================================
+
+Copyright 2005-2020 The Mumble Developers. All rights reserved.
+Use of this source code is governed by a BSD-style license
+that can be found in the LICENSE file at the root of the
+Mumble source tree or at .
+'''
+
+import pytest
+
+from .crypto import CryptStateOCB2, AES_BLOCK_SIZE, EncryptFailedException, DecryptFailedException
+from .crypto import ocb_encrypt, ocb_decrypt
+
+
+@pytest.fixture()
+def rawkey():
+ return bytes((0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f))
+
+
+@pytest.fixture()
+def nonce():
+ return bytes((0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00))
+
+
+def test_reverserecovery():
+ enc = CryptStateOCB2()
+ dec = CryptStateOCB2()
+ enc.gen_key()
+
+ # For our testcase, we're going to FORCE iv
+ enc.encrypt_iv = bytes((0x55,) * AES_BLOCK_SIZE)
+ dec.set_key(enc.raw_key, enc.decrypt_iv, enc.encrypt_iv)
+
+ secret = "abcdefghi".encode('ascii')
+
+ crypted = [enc.encrypt(secret) for _ in range(128)]
+
+ i = 0
+ for crypt in reversed(crypted[-30:]):
+ print(i)
+ i += 1
+ dec.decrypt(crypt, len(secret))
+ for crypt in reversed(crypted[:-30]):
+ with pytest.raises(DecryptFailedException):
+ dec.decrypt(crypt, len(secret))
+ for crypt in reversed(crypted[-30:]):
+ with pytest.raises(DecryptFailedException):
+ dec.decrypt(crypt, len(secret))
+
+ # Extensive replay attack test
+ crypted = [enc.encrypt(secret) for _ in range(512)]
+
+ for crypt in crypted:
+ decr = dec.decrypt(crypt, len(secret))
+ for crypt in crypted:
+ with pytest.raises(DecryptFailedException):
+ dec.decrypt(crypt, len(secret))
+
+
+def test_ivrecovery():
+ enc = CryptStateOCB2()
+ dec = CryptStateOCB2()
+ enc.gen_key()
+
+ # For our testcase, we're going to FORCE iv
+ enc.encrypt_iv = bytes((0x55,) * AES_BLOCK_SIZE)
+ dec.set_key(enc.raw_key, enc.decrypt_iv, enc.encrypt_iv)
+
+ secret = "abcdefghi".encode('ascii')
+
+ crypted = enc.encrypt(secret)
+
+ # Can decrypt
+ decr = dec.decrypt(crypted, len(secret))
+ # ... correctly.
+ assert secret == decr
+
+ # But will refuse to reuse same IV.
+ with pytest.raises(DecryptFailedException):
+ dec.decrypt(crypted, len(secret))
+
+ # Recover from lost packet.
+ for i in range(16):
+ crypted = enc.encrypt(secret)
+ decr = dec.decrypt(crypted, len(secret))
+
+ # Wraparound.
+ for i in range(128):
+ dec.uiLost = 0
+ for j in range(15):
+ crypted = enc.encrypt(secret)
+ decr = dec.decrypt(crypted, len(secret))
+ assert dec.uiLost == 14
+
+ assert enc.encrypt_iv == dec.decrypt_iv
+
+ # Wrap too far
+ for i in range(257):
+ crypted = enc.encrypt(secret);
+
+ with pytest.raises(DecryptFailedException):
+ dec.decrypt(crypted, len(secret))
+
+ # Sync it
+ dec.decrypt_iv = enc.encrypt_iv
+ crypted = enc.encrypt(secret)
+
+ decr = dec.decrypt(crypted, len(secret))
+
+def test_testvectors(rawkey):
+ # Test vectors are from draft-krovetz-ocb-00.txt
+ cs = CryptStateOCB2()
+ cs.set_key(rawkey, rawkey, rawkey)
+
+ _, tag = ocb_encrypt(cs._aes, bytes(), rawkey)
+
+ blanktag = bytes((0xBF,0x31,0x08,0x13,0x07,0x73,0xAD,0x5E,0xC7,0x0E,0xC6,0x9E,0x78,0x75,0xA7,0xB0))
+ assert len(blanktag) == AES_BLOCK_SIZE
+
+ assert tag == blanktag
+
+ source = bytes(range(40))
+ crypt, tag = ocb_encrypt(cs._aes, source, rawkey)
+ longtag = bytes((0x9D,0xB0,0xCD,0xF8,0x80,0xF7,0x3E,0x3E,0x10,0xD4,0xEB,0x32,0x17,0x76,0x66,0x88))
+ crypted = bytes((0xF7,0x5D,0x6B,0xC8,0xB4,0xDC,0x8D,0x66,0xB8,0x36,0xA2,0xB0,0x8B,0x32,0xA6,0x36,0x9F,0x1C,0xD3,0xC5,0x22,0x8D,0x79,0xFD,
+ 0x6C,0x26,0x7F,0x5F,0x6A,0xA7,0xB2,0x31,0xC7,0xDF,0xB9,0xD5,0x99,0x51,0xAE,0x9C))
+
+ assert tag == longtag
+ assert crypt[:len(crypted)] == crypted
+
+
+def test_authcrypt(rawkey, nonce):
+ cs = CryptStateOCB2()
+ for ll in range(128):
+ cs.set_key(rawkey, nonce, nonce)
+ src = bytes((i + 1 for i in range(ll)))
+
+ encrypted, enctag = ocb_encrypt(cs._aes, src, nonce)
+ decrypted, dectag = ocb_decrypt(cs._aes, encrypted, nonce, len(src))
+
+ assert enctag == dectag
+ assert src == decrypted
+
+
+def test_xexstarAttack(rawkey, nonce):
+ ''' Test prevention of the attack described in section 4.1 of https://eprint.iacr.org/2019/311 '''
+ cs = CryptStateOCB2()
+ cs.set_key(rawkey, nonce, nonce)
+
+ # Set first block to `len(secondBlock)`
+ # Set second block to arbitrary value
+ src = bytearray(AES_BLOCK_SIZE) + bytearray([42] * AES_BLOCK_SIZE)
+ src[AES_BLOCK_SIZE - 1] = AES_BLOCK_SIZE * 8
+ print(src)
+
+ with pytest.raises(EncryptFailedException):
+ ocb_encrypt(cs._aes, src, nonce)
+ encrypted, enctag = ocb_encrypt(cs._aes, src, nonce, insecure=True)
+
+ # Perform the attack
+ encrypted = bytearray(encrypted)
+ enctag = bytearray(enctag)
+ encrypted[AES_BLOCK_SIZE - 1] ^= AES_BLOCK_SIZE * 8
+ for i in range(AES_BLOCK_SIZE):
+ enctag[i] = src[AES_BLOCK_SIZE + i] ^ encrypted[AES_BLOCK_SIZE + i]
+
+ with pytest.raises(DecryptFailedException):
+ dc, dct = ocb_decrypt(cs._aes, encrypted, nonce, AES_BLOCK_SIZE)
+ print(dc, dct, enctag == dct)
+ decrypted, dectag = ocb_decrypt(cs._aes, encrypted, nonce, AES_BLOCK_SIZE, insecure=True)
+
+ # Verify forged tag (should match if attack is properly implemented)
+ assert enctag == dectag
+
+
+def test_tamper(rawkey, nonce):
+ cs = CryptStateOCB2()
+ cs.set_key(rawkey, nonce, nonce)
+
+ msg = "It was a funky funky town!".encode('ascii')
+ encrypted = bytearray(cs.encrypt(msg))
+
+ for i in range(len(msg) * 8):
+ encrypted[i // 8] ^= 1 << (i % 8)
+ with pytest.raises(DecryptFailedException):
+ cs.decrypt(encrypted, len(msg))
+ encrypted[i // 8] ^= 1 << (i % 8)
+ decrypted = cs.decrypt(encrypted, len(msg))
diff --git a/pymumble_py3/tools.py b/pymumble_py3/tools.py
new file mode 100644
index 0000000..3531913
--- /dev/null
+++ b/pymumble_py3/tools.py
@@ -0,0 +1,128 @@
+# -*- coding: utf-8 -*-
+import struct
+
+
+class InvalidVarInt(Exception):
+ pass
+
+
+class VarInt:
+ """Implement the varint type used in mumble"""
+ def __init__(self, value=0):
+ self.value = value
+
+ def encode(self):
+ """Encode an integer in the VarInt format, returning a binary string"""
+ result = bytearray()
+ value = abs(self.value)
+
+ if self.value < 0:
+ if self.value >= -3:
+ return struct.pack("!B", (0b11111100 | value))
+ else:
+ result = struct.pack("!B", 0b11111000)
+
+ if value <= 0x7f:
+ return result + struct.pack("!B", value)
+ elif value <= 0x3fff:
+ return result + struct.pack("!H", 0x8000 | value)
+ elif value <= 0x1fffff:
+ return result + struct.pack("!BH", 0xc0 | (value >> 16), 0xffff & value)
+ elif value <= 0xfffffff:
+ return result + struct.pack("!L", 0xe0000000 | value)
+ elif value <= 0xffffffff:
+ return result + struct.pack("!BL", 0b11110000, value)
+ else:
+ return result + struct.pack("!BQ", 0b11110100, value)
+
+ def decode(self, value):
+ """Decode a VarInt contained in a binary string, returning an integer"""
+ varint = value
+ is_negative = False
+ result = None
+ size = 0
+
+ if len(varint) <= 0:
+ raise InvalidVarInt("length can't be 0")
+
+ (first, ) = struct.unpack("!B", varint[0:1])
+
+ if first & 0b11111100 == 0b11111000:
+ is_negative = True
+ size += 1
+ if len(varint) < 2:
+ raise InvalidVarInt("Too short negative varint")
+ varint = varint[1:]
+ (first, ) = struct.unpack("!B", varint[0:1])
+
+ if first & 0b10000000 == 0b00000000:
+ (result, ) = struct.unpack("!B", varint[0:1])
+ size += 1
+ elif first & 0b11111100 == 0b11111100:
+ (result, ) = struct.unpack("!B", varint[0:1])
+ result &= 0b00000011
+ is_negative = True
+ size += 1
+ elif first & 0b11000000 == 0b10000000:
+ if len(varint) < 2:
+ raise InvalidVarInt("Too short 2 bytes varint")
+ (result, ) = struct.unpack("!H", varint[:2])
+ result &= 0b0011111111111111
+ size += 2
+ elif first & 0b11100000 == 0b11000000:
+ if len(varint) < 3:
+ raise InvalidVarInt("Too short 3 bytes varint")
+ (result, ) = struct.unpack("!B", varint[0:1])
+ result &= 0b00011111
+ (tmp, ) = struct.unpack("!H", varint[1:3])
+ result = (result << 16) + tmp
+ size += 3
+ elif first & 0b11110000 == 0b11100000:
+ if len(varint) < 4:
+ raise InvalidVarInt("Too short 4 bytes varint")
+ (result, ) = struct.unpack("!L", varint[:4])
+ result &= 0x0fffffff
+ size += 4
+ elif first & 0b11111100 == 0b11110000:
+ if len(varint) < 5:
+ raise InvalidVarInt("Too short 5 bytes varint")
+ (result, ) = struct.unpack("!L", varint[1:5])
+ size += 5
+ elif first & 0b11111100 == 0b11110100:
+ if len(varint) < 9:
+ raise InvalidVarInt("Too short 9 bytes varint")
+ (result, ) = struct.unpack("!Q", varint[1:9])
+ size += 9
+
+ if is_negative:
+ self.value = - result
+ else:
+ self.value = result
+
+ return size
+
+
+def tohex(buffer):
+ """Used for debugging. Output a sting in hex format"""
+ result = "\n"
+ cpt1 = 0
+ cpt2 = 0
+
+ for byte in buffer:
+ result += hex(ord(byte))[2:].zfill(2)
+ cpt1 += 1
+
+ if cpt1 >= 4:
+ result += " "
+ cpt1 = 0
+ cpt2 += 1
+
+ if cpt2 >= 10:
+ result += "\n"
+ cpt2 = 0
+
+ return result
+
+
+
+
diff --git a/pymumble_py3/users.py b/pymumble_py3/users.py
new file mode 100644
index 0000000..6b87ab8
--- /dev/null
+++ b/pymumble_py3/users.py
@@ -0,0 +1,283 @@
+# -*- coding: utf-8 -*-
+from .constants import *
+from .errors import TextTooLongError, ImageTooBigError
+from threading import Lock
+from . import messages
+from . import mumble_pb2
+
+
+class Users(dict):
+ """Object that stores and update all connected users"""
+
+ def __init__(self, mumble_object, callbacks):
+ self.mumble_object = mumble_object
+ self.callbacks = callbacks
+
+ self.myself = None # user object of the pymumble thread itself
+ self.myself_session = None # session number of the pymumble thread itself
+ self.lock = Lock()
+
+ def update(self, message):
+ """Update a user information, based on the incoming message"""
+ self.lock.acquire()
+
+ if message.session not in self:
+ self[message.session] = User(self.mumble_object, message)
+ self.callbacks(PYMUMBLE_CLBK_USERCREATED, self[message.session])
+ if message.session == self.myself_session:
+ self.myself = self[message.session]
+ else:
+ actions = self[message.session].update(message)
+ self.callbacks(PYMUMBLE_CLBK_USERUPDATED, self[message.session], actions)
+
+ self.lock.release()
+
+ def remove(self, message):
+ """Remove a user object based on server info"""
+ self.lock.acquire()
+
+ if message.session in self:
+ user = self[message.session]
+ del self[message.session]
+ self.callbacks(PYMUMBLE_CLBK_USERREMOVED, user, message)
+
+ self.lock.release()
+
+ def set_myself(self, session):
+ """Set the "myself" user"""
+ self.myself_session = session
+ if session in self:
+ self.myself = self[session]
+
+ def count(self):
+ """Return the count of connected users"""
+ return len(self)
+
+
+class User(dict):
+ """Object that store one user"""
+
+ def __init__(self, mumble_object, message):
+ self.mumble_object = mumble_object
+ self["session"] = message.session
+ self["channel_id"] = 0
+ self.update(message)
+
+ if mumble_object.receive_sound:
+ from . import soundqueue
+ self.sound = soundqueue.SoundQueue(self.mumble_object) # will hold this user incoming audio
+ else:
+ self.sound = None
+
+ def update(self, message):
+ """Update user state, based on an incoming message"""
+ actions = dict()
+
+ if message.HasField("actor"):
+ actions["actor"] = message.actor
+
+ for (field, value) in message.ListFields():
+ if field.name in ("session", "actor", "comment", "texture", "plugin_context", "plugin_identity"):
+ continue
+ actions.update(self.update_field(field.name, value))
+
+ if message.HasField("comment_hash"):
+ if message.HasField("comment"):
+ self.mumble_object.blobs[message.comment_hash] = message.comment
+ else:
+ self.mumble_object.blobs.get_user_comment(message.comment_hash)
+ if message.HasField("texture_hash"):
+ if message.HasField("texture"):
+ self.mumble_object.blobs[message.texture_hash] = message.texture
+ else:
+ self.mumble_object.blobs.get_user_texture(message.texture_hash)
+
+ return actions # return a dict, useful for the callback functions
+
+ def update_field(self, name, field):
+ """Update one state value for a user"""
+ actions = dict()
+ if name not in self or self[name] != field:
+ self[name] = field
+ actions[name] = field
+
+ return actions
+
+ def get_property(self, property):
+ if property in self:
+ return self[property]
+ else:
+ return None
+
+ def mute(self):
+ """Mute a user"""
+ params = {"session": self["session"]}
+
+ if self["session"] == self.mumble_object.users.myself_session:
+ params["self_mute"] = True
+ else:
+ params["mute"] = True
+
+ cmd = messages.ModUserState(self.mumble_object.users.myself_session, params)
+ self.mumble_object.execute_command(cmd)
+
+ def unmute(self):
+ """Unmute a user"""
+ params = {"session": self["session"]}
+
+ if self["session"] == self.mumble_object.users.myself_session:
+ params["self_mute"] = False
+ else:
+ params["mute"] = False
+
+ cmd = messages.ModUserState(self.mumble_object.users.myself_session, params)
+ self.mumble_object.execute_command(cmd)
+
+ def deafen(self):
+ """Deafen a user"""
+ params = {"session": self["session"]}
+
+ if self["session"] == self.mumble_object.users.myself_session:
+ params["self_deaf"] = True
+ else:
+ params["deaf"] = True
+
+ cmd = messages.ModUserState(self.mumble_object.users.myself_session, params)
+ self.mumble_object.execute_command(cmd)
+
+ def undeafen(self):
+ """Undeafen a user"""
+ params = {"session": self["session"]}
+
+ if self["session"] == self.mumble_object.users.myself_session:
+ params["self_deaf"] = False
+ else:
+ params["deaf"] = False
+
+ cmd = messages.ModUserState(self.mumble_object.users.myself_session, params)
+ self.mumble_object.execute_command(cmd)
+
+ def suppress(self):
+ """Disable a user"""
+ params = {"session": self["session"],
+ "suppress": True}
+
+ cmd = messages.ModUserState(self.mumble_object.users.myself_session, params)
+ self.mumble_object.execute_command(cmd)
+
+ def unsuppress(self):
+ """Enable a user"""
+ params = {"session": self["session"],
+ "suppress": False}
+
+ cmd = messages.ModUserState(self.mumble_object.users.myself_session, params)
+ self.mumble_object.execute_command(cmd)
+
+ def recording(self):
+ """Set the user as recording"""
+ params = {"session": self["session"],
+ "recording": True}
+
+ cmd = messages.ModUserState(self.mumble_object.users.myself_session, params)
+ self.mumble_object.execute_command(cmd)
+
+ def unrecording(self):
+ """Set the user as not recording"""
+ params = {"session": self["session"],
+ "recording": False}
+
+ cmd = messages.ModUserState(self.mumble_object.users.myself_session, params)
+ self.mumble_object.execute_command(cmd)
+
+ def comment(self, comment):
+ """Set the user comment"""
+ params = {"session": self["session"],
+ "comment": comment}
+
+ cmd = messages.ModUserState(self.mumble_object.users.myself_session, params)
+ self.mumble_object.execute_command(cmd)
+
+ def texture(self, texture):
+ """Set the user texture"""
+ params = {"session": self["session"],
+ "texture": texture}
+
+ cmd = messages.ModUserState(self.mumble_object.users.myself_session, params)
+ self.mumble_object.execute_command(cmd)
+
+ def register(self):
+ """Register the user (mostly for myself)"""
+ params = {"session": self["session"],
+ "user_id": 0}
+
+ cmd = messages.ModUserState(self.mumble_object.users.myself_session, params)
+ self.mumble_object.execute_command(cmd)
+
+ def update_context(self, context_name):
+ params = {"session": self["session"],
+ "plugin_context": context_name}
+ cmd = messages.ModUserState(self.mumble_object.users.myself_session, params)
+ self.mumble_object.execute_command(cmd)
+
+ def move_in(self, channel_id, token=None):
+ if token:
+ authenticate = mumble_pb2.Authenticate()
+ authenticate.username = self.mumble_object.user
+ authenticate.password = self.mumble_object.password
+ authenticate.tokens.extend(self.mumble_object.tokens)
+ authenticate.tokens.extend([token])
+ authenticate.opus = True
+ self.mumble_object.Log.debug("sending: authenticate: %s", authenticate)
+ self.mumble_object.send_message(PYMUMBLE_MSG_TYPES_AUTHENTICATE, authenticate)
+
+ session = self.mumble_object.users.myself_session
+ cmd = messages.MoveCmd(session, channel_id)
+ self.mumble_object.execute_command(cmd)
+
+ def send_text_message(self, message):
+ """Send a text message to the user."""
+
+ # TODO: This check should be done inside execute_command()
+ # However, this is currently not possible because execute_command() does
+ # not actually execute the command.
+ if len(message) > self.mumble_object.get_max_image_length() != 0:
+ raise ImageTooBigError(self.mumble_object.get_max_image_length())
+
+ if not (" self.mumble_object.get_max_message_length() != 0:
+ raise TextTooLongError(self.mumble_object.get_max_message_length())
+
+ cmd = messages.TextPrivateMessage(self["session"], message)
+ self.mumble_object.execute_command(cmd)
+
+ def kick(self, reason=""):
+ params = {"session": self["session"],
+ "reason": reason,
+ "ban": False}
+
+ cmd = messages.RemoveUser(self.mumble_object.users.myself_session, params)
+ self.mumble_object.execute_command(cmd)
+
+ def ban(self, reason=""):
+ params = {"session": self["session"],
+ "reason": reason,
+ "ban": True}
+
+ cmd = messages.RemoveUser(self.mumble_object.users.myself_session, params)
+ self.mumble_object.execute_command(cmd)
+
+ def add_listening_channels(self, channel):
+ """Add user to listening channel"""
+ params = {"session": self["session"],
+ "listening_channel_add": channel}
+
+ cmd = messages.ModUserState(self.mumble_object.users.myself_session, params)
+ self.mumble_object.execute_command(cmd)
+
+ def remove_listening_channels(self, channel):
+ """Remove user from listening channel"""
+ params = {"session": self["session"],
+ "listening_channel_remove": channel}
+
+ cmd = messages.ModUserState(self.mumble_object.users.myself_session, params)
+ self.mumble_object.execute_command(cmd)
diff --git a/requirements.txt b/requirements.txt
index 8c7ea5d..652a3da 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,5 +4,5 @@ Pillow
mutagen
requests
packaging
-pymumble>=1.2
-pyradios
\ No newline at end of file
+pyradios
+opuslib==3.0.1
\ No newline at end of file
diff --git a/scripts/commit_new_translation.sh b/scripts/commit_new_translation.sh
deleted file mode 100755
index 14ef4df..0000000
--- a/scripts/commit_new_translation.sh
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/usr/bin/env bash
-
-git remote set-url origin https://azlux:$GITHUB_API@github.com/azlux/botamusique/
-
-echo "=> Fetching for bot-traduora branch..."
-if git fetch origin bot-traduora; then
- echo "==> bot-traduora branch exists"
- git branch bot-traduora FETCH_HEAD
- CREATE_PR=false
-else
- echo "==> bot-traduora branch doesn't exist, create one"
- git branch bot-traduora
- CREATE_PR=true
-fi
-git checkout bot-traduora
-
-echo "=> Fetching updates from the server..."
-
-$SOURCE_DIR/scripts/sync_translation.py --lang-dir $SOURCE_DIR/lang/ --client $TRADUORA_R_CLIENT --secret $TRADUORA_R_SECRET --fetch
-git add lang/*
-git status
-
-if [ "$PUSH" = "true" ]; then
- echo "=> Pushing updates to bot-traduora branch..."
- if GIT_COMMITTER_NAME='Traduora Bot' GIT_COMMITTER_EMAIL='noreply@azlux.fr' git commit -m 'Bot: Update translation' --author "Traduora Bot "; then
- git push origin bot-traduora
- sleep 2
- if $CREATE_PR; then GITHUB_USER="azlux" GITHUB_TOKEN="$GITHUB_API" hub pull-request -m "Bot: TRADUORA Update"; fi
- exit 0
- fi
- echo "==> There's nothing to push."
- exit 0
-fi
diff --git a/scripts/sync_translation.py b/scripts/sync_translation.py
deleted file mode 100755
index 87ecc57..0000000
--- a/scripts/sync_translation.py
+++ /dev/null
@@ -1,101 +0,0 @@
-#!/usr/bin/env python3
-
-import os
-import re
-import argparse
-import requests
-
-base_url = "https://translate.azlux.fr/api/v1"
-project_id = "4aafb197-3282-47b3-a197-0ca870cf6ab2"
-
-lang_dir = ""
-
-
-def get_access_header(client, secret):
- data = {"grant_type": "client_credentials",
- "client_id": client,
- "client_secret": secret}
-
- r = requests.post(f"{base_url}/auth/token", json=data)
-
- if r.status_code != 200:
- print("Access denied! Please check your client ID or secret.")
- exit(1)
-
- token = r.json()["access_token"]
-
- headers = {"Authorization": "Bearer " + token,
- "Accept": "application/json, text/plain, */*"}
-
- return headers
-
-
-def fetch_translation(r_client, r_secret):
- headers = get_access_header(r_client, r_secret)
-
- r = requests.get(f"{base_url}/projects/{project_id}/translations", headers=headers)
- translations = r.json()['data']
- for translation in translations:
- lang_code = translation['locale']['code']
- print(f" - Fetching {lang_code}")
- params = {'locale': lang_code,
- 'format': 'jsonnested'}
- r = requests.get(f"{base_url}/projects/{project_id}/exports", params=params, headers=headers)
- with open(os.path.join(lang_dir, f"{lang_code}.json"), "wb") as f:
- f.write(r.content)
-
-
-def push_strings(w_client, w_secret):
- print("Pushing local translation files into the remote host...")
- headers = get_access_header(w_client, w_secret)
-
- lang_files = os.listdir(lang_dir)
- lang_list = []
- for lang_file in lang_files:
- match = re.search("([a-z]{2}_[A-Z]{2})\.json", lang_file)
- if match:
- lang_list.append(match[1])
-
- for lang in lang_list:
- print(f" - Pushing {lang}")
-
- params = {'locale': lang,
- 'format': 'jsonnested'}
- files = {'file': open(os.path.join(lang_dir, f"{lang}.json"), 'r')}
-
- r = requests.post(f"{base_url}/projects/{project_id}/imports", params=params, headers=headers, files=files)
- assert r.status_code == 200, f"Unable to push {lang} into remote host. {r.status_code}"
-
-
-if __name__ == "__main__":
- parser = argparse.ArgumentParser(
- description="Sync translation files with azlux's traduora server.")
-
- parser.add_argument("--lang-dir", dest="lang_dir",
- type=str, help="Directory of the lang files.")
- parser.add_argument("--client", dest="client",
- type=str, help="Client ID used to access the server.")
- parser.add_argument("--secret", dest="secret",
- type=str, help="Secret used to access the server.")
-
- parser.add_argument("--fetch", dest='fetch', action="store_true",
- help='Fetch translation files from the server.')
- parser.add_argument("--push", dest='push', action="store_true",
- help='Push local translation files into the server.')
-
- args = parser.parse_args()
-
- lang_dir = args.lang_dir
-
- if not args.client or not args.secret:
- print("Client ID and secret need to be provided!")
- exit(1)
-
- if args.push:
- push_strings(args.client, args.secret)
-
- if args.fetch:
- fetch_translation(args.client, args.secret)
-
- print("Done.")
-
diff --git a/scripts/translate_templates.py b/scripts/translate_templates.py
deleted file mode 100755
index 6bd1983..0000000
--- a/scripts/translate_templates.py
+++ /dev/null
@@ -1,75 +0,0 @@
-#!/usr/bin/env python3
-import argparse
-import os
-import json
-import re
-import jinja2
-
-default_lang_dict = {}
-lang_dict = {}
-
-lang_dir = ""
-template_dir = ""
-
-
-def load_lang(lang):
- with open(os.path.join(lang_dir, f"{lang}.json"), "r") as f:
- return json.load(f)
-
-
-def tr(option):
- try:
- if option in lang_dict['web'] and lang_dict['web'][option]:
- string = lang_dict['web'][option]
- else:
- string = default_lang_dict['web'][option]
- return string
- except KeyError:
- raise KeyError("Missed strings in language file: '{string}'. "
- .format(string=option))
-
-
-if __name__ == "__main__":
- parser = argparse.ArgumentParser(
- description="Populate html templates with translation strings.")
-
- parser.add_argument("--lang-dir", dest="lang_dir",
- type=str, help="Directory of the lang files.")
- parser.add_argument("--template-dir", dest="template_dir",
- type=str, help="Directory of the template files.")
-
- args = parser.parse_args()
- lang_dir = args.lang_dir
- template_dir = args.template_dir
-
- html_files = os.listdir(template_dir)
- for html_file in html_files:
- match = re.search("(.+)\.template\.html", html_file)
- if match is None:
- continue
-
- print(f"Populating {html_file} with translations...")
- basename = match[1]
- with open(os.path.join(template_dir, f"{html_file}"), "r") as f:
- html = f.read()
-
- lang_files = os.listdir(lang_dir)
- lang_list = []
-
- default_lang_dict = load_lang("en_US")
-
- for lang_file in lang_files:
- match = re.search("([a-z]{2}_[A-Z]{2})\.json", lang_file)
- if match:
- lang_list.append(match[1])
-
- template = jinja2.Template(html)
-
- for lang in lang_list:
- print(f" - Populating {lang}...")
- lang_dict = load_lang(lang)
-
- with open(os.path.join(template_dir, f"{basename}.{lang}.html"),
- "w") as f:
- f.write(template.render(tr=tr))
- print("Done.")
diff --git a/scripts/update_translation_to_server.sh b/scripts/update_translation_to_server.sh
deleted file mode 100755
index 3a16b06..0000000
--- a/scripts/update_translation_to_server.sh
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/usr/bin/env bash
-
-set -e
-
-git remote set-url origin https://azlux:$GITHUB_API@github.com/azlux/botamusique/
-git pull origin master
-
-echo "=> Checking if translations in this commit differ from the server..."
-
-git branch testing-translation master
-git checkout testing-translation
-$SOURCE_DIR/scripts/sync_translation.py --lang-dir $SOURCE_DIR/lang/ --client $TRADUORA_R_CLIENT --secret $TRADUORA_R_SECRET --fetch
-
-if [ -z "$(git diff)" ]; then
- echo "==> No difference found."
- exit 0
-fi
-
-echo "==> Modifications found."
-echo "=> Check if the modifications are based on the translations on the server..."
-
-n=1
-COMMON_FOUND=false
-
-while [ $n -le 20 ]; do
- echo "==> Comparing server's translations with master~$n ($(git show --oneline --quiet master~$n))"
- CHANGED_LANG_FILE=$(git diff --name-only master~$n | grep "lang/" || true)
- if [ -z "$CHANGED_LANG_FILE" ]; then
- COMMON_FOUND=true
- break
- else
- echo "==> Modified lang files: $CHANGED_LANG_FILE"
- fi
- (( n++ ))
-done
-
-if (! $COMMON_FOUND); then
- echo "==> CONFLICTS: Previous commits doesn't share the same translations with the server."
- echo " There are unmerged translation updates on the server."
- echo " Please manually update these changes or wait for the pull request"
- echo " created by the translation bot get merged."
- exit 1
-fi
-
-echo "==> master~$n ($(git show --oneline --quiet master~$n)) shares the same translations with the server."
-
-echo "=> Preparing to push local translation updates to the server..."
-git checkout -f master
-$SOURCE_DIR/scripts/sync_translation.py --lang-dir $SOURCE_DIR/lang/ --client $TRADUORA_W_CLIENT --secret $TRADUORA_W_SECRET --push
-
-echo "=> Fix translation format..."
-$SOURCE_DIR/scripts/sync_translation.py --lang-dir $SOURCE_DIR/lang/ --client $TRADUORA_R_CLIENT --secret $TRADUORA_R_SECRET --fetch
-git add lang/*
-git status
-if GIT_COMMITTER_NAME='Traduora Bot' GIT_COMMITTER_EMAIL='noreply@azlux.fr' git commit -m 'Bot: Reformat translation'; then
- git push origin master
-fi
-
-exit 0
diff --git a/static/.gitignore b/static/.gitignore
deleted file mode 100644
index 4eb78bf..0000000
--- a/static/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-css/
-js/
\ No newline at end of file
diff --git a/static/image/empty_box.svg b/static/image/empty_box.svg
deleted file mode 100644
index f896567..0000000
--- a/static/image/empty_box.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/static/image/favicon.ico b/static/image/favicon.ico
deleted file mode 100644
index 75245a1..0000000
Binary files a/static/image/favicon.ico and /dev/null differ
diff --git a/static/image/loading.svg b/static/image/loading.svg
deleted file mode 100644
index 6bbd482..0000000
--- a/static/image/loading.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/static/image/logo.png b/static/image/logo.png
deleted file mode 100644
index c2d0ccb..0000000
Binary files a/static/image/logo.png and /dev/null differ
diff --git a/static/image/play-plus.svg b/static/image/play-plus.svg
deleted file mode 100644
index 4b00843..0000000
--- a/static/image/play-plus.svg
+++ /dev/null
@@ -1,14 +0,0 @@
-
\ No newline at end of file
diff --git a/static/image/playlist-plus.svg b/static/image/playlist-plus.svg
deleted file mode 100644
index 2cb0961..0000000
--- a/static/image/playlist-plus.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/static/image/unknown-album.png b/static/image/unknown-album.png
deleted file mode 100644
index 50455c5..0000000
Binary files a/static/image/unknown-album.png and /dev/null differ
diff --git a/update.sh b/update.sh
deleted file mode 100644
index b13786b..0000000
--- a/update.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/env bash
-
-case "$1" in
- stable)
- curl -Lo /tmp/bragi.tar.gz https://packages.azlux.fr/bragi/sources-stable.tar.gz
- tar -xzf /tmp/bragi.tar.gz -C /tmp/
- cp -r /tmp/bragi/* .
- rm -r /tmp/bragi
- rm -r /tmp/bragi.tar.gz
- ;;
- testing)
- curl -Lo /tmp/bragi.tar.gz https://packages.azlux.fr/bragi/sources-testing.tar.gz
- tar -xzf /tmp/bragi.tar.gz -C /tmp/
- cp -r /tmp/bragi/* .
- rm -r /tmp/bragi
- rm -r /tmp/bragi.tar.gz
- ;;
- *)
- ;;
-esac
-exit 0