first commit

This commit is contained in:
Stanislav Vishnevskiy
2013-10-06 16:12:35 -07:00
parent e2a9177a7d
commit dab98d2f86
9 changed files with 476 additions and 1 deletions

View File

@@ -1,4 +1,4 @@
mumblepy
========
Python Mumble for Humans™.
WORK IN PROGRESS

3
mumble/__init__.py Normal file
View File

@@ -0,0 +1,3 @@
from .meta import Meta
from .hooks import *
from .server import Server

23
mumble/channel.py Normal file
View File

@@ -0,0 +1,23 @@
class Channel(object):
def __init__(self, server, channel):
self.__server = server
self.__channel = channel
def delete(self):
self.__server.remove_channel(self.__channel.id)
def update(self, **kwargs):
for key, value in kwargs.items():
setattr(self.__channel, key, value)
self.__server.set_channel_state(self.__channel)
def serialize(self):
return {
'id': self.__channel.id,
'parent': self.__channel.parent,
'links': self.__channel.links,
'name': self.__channel.name,
'description': self.__channel.description,
'temporary': self.__channel.temporary,
'position': self.__channel.position,
}

117
mumble/hooks.py Normal file
View File

@@ -0,0 +1,117 @@
class MetaCallback(object):
definition = ('MetaCallback', 'addCallback', 'removeCallback')
def __init__(self, meta):
self.meta = meta
def started(self, server):
"""Called when a server is started. The server is up and running when this event is sent,
so all methods that need a running server will work."""
pass
def stopped(self, server):
"""Called when a server is stopped. The server is already stopped when this event is sent,
so no methods that need a running server will work."""
pass
class ServerCallback(object):
definition = ('ServerCallback', 'addCallback', 'removeCallback')
def __init__(self, server_id):
self.id = server_id
def user_connected(self, state):
"""Called when a user connects to the server. """
pass
def user_disconnected(self, state):
"""Called when a user disconnects from the server."""
pass
def user_state_changed(self, state):
"""Called when a user state changes. This is called if the user moves, is renamed, is muted,
deafened etc."""
pass
def user_text_message(self, state, message):
"""Called when user writes a text message."""
pass
def channel_created(self, state):
"""Called when a new channel is created."""
pass
def channel_removed(self, state):
"""Called when a channel is removed."""
pass
def channel_state_changed(self, state):
"""Called when a new channel state changes. This is called if the channel is moved, renamed
or if new links are added."""
pass
class ServerContextCallback(object):
definition = ('ServerContextCallback', 'addContextCallback', 'removeContextCallback')
def __init__(self, server_id):
self.id = server_id
def context_action(self, action, user, session, channelid):
pass
class ServerAuthenticator(object):
definition = ('ServerAuthenticator', 'setAuthenticator', None)
fallthrough_values = dict(
authenticate=(-2, None, None),
get_info=(False, None,),
name_to_id=-2,
id_to_name='',
id_to_texture=None,
)
def __init__(self, server_id):
self.id = server_id
def authenticate(self, name, password, certificates, certhash, certstrong):
raise NotImplementedError
def get_info(self, user_id):
raise NotImplementedError
def name_to_id(self, name):
raise NotImplementedError
def id_to_name(self, user_id):
raise NotImplementedError
def id_to_texture(self, user_id):
raise NotImplementedError
class ServerUpdatingAuthenticator(ServerAuthenticator):
definition = ('ServerUpdatingAuthenticator', 'setAuthenticator', None)
fallthrough_values = dict(
register_user=-2,
unregister_user=-1,
get_registered_users={},
set_info=-1,
set_texture=-1,
)
def register_user(self, info):
raise NotImplementedError
def unregister_user(self, user_id):
raise NotImplementedError
def get_registered_users(self, filter):
raise NotImplementedError
def set_info(self, user_id, info):
raise NotImplementedError
def set_texture(self, user_id, texture):
raise NotImplementedError

37
mumble/iceutil.py Normal file
View File

@@ -0,0 +1,37 @@
import re
def ice_method(attr):
# Convert event name from CamelCase to underscores.
attr = re.sub(r'(.)([A-Z][a-z]+)', r'\1_\2', attr)
attr = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', attr).lower()
# Strip out the `current` value which is the final one alwaus.
return lambda self, *args: getattr(self.callback, attr)(*args[:-1])
def ice_callback(name, bases, attrs):
def __init__(self, callback):
self.callback = callback
attrs['__init__'] = __init__
# Detect Ice methods and wrap in more pythonic callbacks.
for base in bases:
for attr in dir(base):
if attr.startswith('_op_') and not attr.startswith('_op_ice_'):
attr = attr[4:]
attrs[attr] = ice_method(attr)
return type(name, bases, attrs)
_ice_class_cache = {}
def ice_init(from_, name, *args, **kwargs):
try:
cls = _ice_class_cache[name]
except KeyError:
class MurmurClass(getattr(from_, name)):
__metaclass__ = ice_callback
cls = _ice_class_cache[name] = MurmurClass
return cls(*args, **kwargs)

134
mumble/meta.py Normal file
View File

@@ -0,0 +1,134 @@
import Ice
import IcePy
import sys
import tempfile
import os
import logging
from .iceutil import ice_init
from .server import Server
class Logger(Ice.Logger):
def _print(self, message):
logging.info(message)
def trace(self, category, message):
pass
def warning(self, message):
logging.warning(message)
def error(self, message):
logging.error(message)
class Meta(object):
def __init__(self, secret=None):
self.secret = secret
self.__meta = None
self.__ice = None
self.__adapter = None
self.connect()
def __del__(self):
self.disconnect()
def load_slice(self, proxy):
mumble_slice = IcePy.Operation(
'getSlice',
Ice.OperationMode.Idempotent,
Ice.OperationMode.Idempotent,
True,
(),
(),
(),
IcePy._t_string,
()
).invoke(proxy, ((), None))
_, temp = tempfile.mkstemp(suffix='.ice')
with open(temp, 'w') as slice_file:
slice_file.write(mumble_slice)
slice_file.flush()
Ice.loadSlice('', ['-I' + Ice.getSliceDir(), temp])
os.remove(temp)
def connect(self):
init_data = Ice.InitializationData()
init_data.properties = Ice.createProperties(sys.argv)
init_data.properties.setProperty('Ice.ImplicitContext', 'Shared')
init_data.logger = Logger()
self.__ice = Ice.initialize(init_data)
if self.secret:
self.__ice.getImplicitContext().put('secret', self.secret)
self.__adapter = self.__ice.createObjectAdapterWithEndpoints('Callback.Client', 'tcp -h 127.0.0.1')
self.__adapter.activate()
proxy = self.__ice.stringToProxy('Meta:tcp -h 127.0.0.1 -p 6502')
self.load_slice(proxy)
import Murmur
self.__meta = Murmur.MetaPrx.checkedCast(proxy)
def disconnect(self):
self.__ice.shutdown()
def add_callback(self, callback):
import Murmur
callback_prx = Murmur.MetaCallbackPrx.uncheckedCast(self.__adapter.addWithUUID(callback))
self.__meta.addCallback(callback_prx)
def remove_callback(self, callback):
self.__meta.removeCallback(callback)
def get_version(self):
return self.__meta.getVersion()
def get_booted_servers(self):
return [Server(self, server) for server in self.__meta.getBootedServers()]
def get_all_servers(self):
return [Server(self, server) for server in self.__meta.getAllServers()]
def get_default_conf(self):
return self.__meta.getDefaultConf()
def new_server(self):
return Server(self, self.__meta.newServer())
def get_server(self, server_id):
server = self.__meta.getServer(server_id)
if server:
return Server(self, server)
return None
def get_uptime(self):
return self.__meta.getUptime()
def add_hook(self, cls):
return self.add_hook_to(self.__meta, cls, self)
def add_hook_to(self, target, cls, *args, **kwargs):
import Murmur
name, add_func_name, _ = cls.definition
hook = ice_init(Murmur, name, cls(*args, **kwargs))
hook_with_uuid = self.__adapter.addWithUUID(hook)
hook_prx = getattr(Murmur, '%sPrx' % name).checkedCast(hook_with_uuid)
return getattr(target, add_func_name)(hook_prx)
def remove_hook(self, cls, hook_prx):
self.remove_hook_from(self.__meta, cls, hook_prx)
def remove_hook_from(self, target, cls, hook_prx):
_, _, remove_func_name = cls.definition
if not remove_func_name:
return
getattr(target, remove_func_name).addCallback(hook_prx)

96
mumble/server.py Normal file
View File

@@ -0,0 +1,96 @@
from .user import User
from .channel import Channel
class Server(object):
def __init__(self, meta, server):
self.id = server.id()
self.__meta = meta
self.__server = server
def __len__(self):
return
@property
def running(self):
return bool(self.__server.isRunning())
def start(self):
if not self.running:
return self.__server.start()
def stop(self):
if self.running:
return self.__server.stop()
def delete(self):
self.stop()
return self.__server.delete()
# Conf
def get_all_conf(self):
conf = self.__meta.get_default_conf()
conf.update(self.__server.getAllConf())
return conf
def get_conf(self, key):
return self.__server.getConf(key)
def set_conf(self, key, value):
return self.__server.setConf(key, value)
# Channels
def get_channels(self):
return [Channel(self, channel) for channel in self.__server.getChannels().values()]
def get_channel(self, channel_id):
channel = self.__server.getChannelState(channel_id)
if channel is None:
return None
return Channel(self, channel)
def set_channel_state(self, channel):
self.__server.setChannelState(channel)
def add_channel(self, name, parent):
return self.__server.addChannel(name, parent)
def remove_channel(self, channel_id):
self.__server.removeChannel(channel_id)
# Users
def get_users(self):
return [User(self, user) for user in self.__server.getUsers().values()]
def get_user(self, session):
user = self.__server.getState(session)
if user is None:
return None
return User(self, user)
def kick_user(self, session, reason=''):
self.__server.kickUser(session, reason)
# Bans
def get_bans(self):
return self.__server.getBans()
def set_bans(self, bans):
self.__server.setBans(bans)
# Hooks
def add_hook(self, cls):
self.__meta.add_hook_to(self.__server, cls, self.id)
def remove_hook(self, cls, hook):
self.__meta.remove_hook_from(self.__server, cls, hook)

38
mumble/user.py Normal file
View File

@@ -0,0 +1,38 @@
import time
class User(object):
def __init__(self, server, user):
self.__server = server
self.__user = user
def ban(self, reason='', bits=128, duration=360):
from Murmur import Ban
bans = self.__server.get_bans()
bans.append(Ban(
reason=reason,
bits=bits,
duration=duration,
start=int(time.time()),
address=self.__user.address,
))
self.__server.set_bans(bans)
def serialize(self):
return {
'session': self.__user.session,
'id': self.__user.userid,
'priority_speaker': self.__user.prioritySpeaker,
'mute': self.__user.mute,
'deaf': self.__user.deaf,
'suppress': self.__user.suppress,
'channel': self.__user.channel,
'name': self.__user.name,
'online_secs': self.__user.onlinesecs,
'comment': self.__user.comment,
'self_mute': self.__user.selfMute,
'self_deaf': self.__user.selfDeaf,
'idle_secs': self.__user.idlesecs,
'ip': '.'.join(map(unicode, self.__user.address[-4:])),
'os': self.__user.osversion
}

27
setup.py Normal file
View File

@@ -0,0 +1,27 @@
# coding=utf-8
from setuptools import setup, find_packages
DESCRIPTION = 'Python Mumble for Humans™'
with open('README.md') as f:
LONG_DESCRIPTION = f.read()
VERSION = '0.1.0'
setup(
name='mumble',
version=VERSION,
packages=find_packages(),
author='Stanislav Vishnevskiy',
author_email='vishnevskiy@gmail.com',
url='https://github.com/vishnevskiy/mumblepy',
license='MIT',
include_package_data=True,
description=DESCRIPTION,
long_description=LONG_DESCRIPTION,
install_requires=[],
platforms=['any'],
classifiers=[],
test_suite='tests',
)