azlux / pymumble

Mumble client implementation in Python
GNU General Public License v3.0
129 stars 61 forks source link

Add support for writing to groups. #92

Closed Alex-Programs closed 3 years ago

Alex-Programs commented 3 years ago

In order to do authentication with bots we need to be able to write permissions. Without it they are useless (as I have discovered, having already written most of a bot.) I would appreciate the ability to write permissions, though I know you are a volunteer maintaining this excellent project and don't expect you to.

Assuming it's something you don't want to add, do you know of a fork that does have it / if I were to add it myself, where should I start? And finally, given that I currently can't use a bot to authenticate (I'm working on bridging Mumble and Discord roles), is there a better alternative / is there a different way to write permissions?

Edit: To clarify, I'd like to add and remove users from a group. It wouldn't need to be created by the bot beforehand. I don't mind about other things, though of course they would make it more feature complete.

CaptainZidgel commented 3 years ago

As far as I know, there is no library with group editing. If you wanted to add it, you'd need to start by learning a bit about Protobufs: The Mumble protocol is implemented by sending these strongly typed messages, here's the structure of an ACL object: https://github.com/mumble-voip/mumble/blob/master/src/Mumble.proto#L317 The ACL object contains a repeated field of all the ACL permission objects, and a repeated field of all the groups. Group objects contain a repeated field of all the user ids explicitly added to a group (you'd likely want to add/remove from this field, unless you're working with inherit-ability which I doubt you are).

What you would probably want to do is copy the results of an ACL query, modify the add fields of the groups field you want to change, which seems simple on paper but I'm not sure how you would go about interacting with repeated fields like this (frankly I find Protobufs overly complicated / out of my league). You would then want to send the message out, which is a lot easier: here's how its done in this library: https://github.com/azlux/pymumble/blob/pymumble_py3/pymumble_py3/mumble.py#L586 Actually now that I'm looking at it, I see how previous authors have constructed repeated fields: https://github.com/azlux/pymumble/blob/pymumble_py3/pymumble_py3/mumble.py#L598 You would probably write something like

aclMessage = #however you're copying the previous group from a query (I do not know if you can just copy the message as it is sent then modify it) #The ACL object you send and receive represents all ACL information for the channel implied in the field channel_id, which can be a significant amount if you're working with the root. Tread carefully when removing fields, as your new message replaces the old one, it does not merge.
group = #pick the group you want, something like #[group for group in aclMessage.groups if group.name == "AUTHENTICATED"]
group.add.append(ID) #removing from .add: https://stackoverflow.com/questions/15307079/how-can-i-remove-an-item-from-a-repeated-protobuf-field-in-python
#You would need to get this group back into aclMessage.groups, I am having a brain fart right now and cannot remember if Python would update the reference

aclMessage.query = False #Not a query, it's an actual change. This defaults to False but I do not know if copying the results of a query will have the query field filled.
self.send_message(PYMUMBLE_MSG_TYPES_ACL, aclMessage) #send message through the Library's API
cmd.response = True #these two lines are necessary, related to threading I think
self.commands.answer(cmd)

Sorry this wasn't really helpful, I just said "you need to figure this stuff out!" with some spaghetti but I am stumped by this problem myself. Hopefully I gave you a good idea of what you need to tinker with.

Update

(not removing my last message, as to preserve my thought process) Here is definitely how you update your ACL message, I've tested it myself

def onacl(msg):
  print("Incoming ACL msg")

  group = next(group for group in msg.groups if group.name == "admin") #getting group named admin, via generator
  group.add.append(4) #I want to add user_id 4 to this group

  #msg is now ready to send back! Query is set to False.

The hard part would now be passing this to the bot to send to the server, I would write a PR but I should be asleep now! Hope this helps.

azlux commented 3 years ago

@AlexAndHisScripts As you can see, LCAs were not implemented in pymumble. Some additions have already been made, like seeing the channel ACLs, adding positioning, and whispering. Thanks @CaptainZidgel for you message, you have write everything about implementing a new Protobuff message.

If you need more example, you can refer to https://github.com/azlux/pymumble/commit/754b4f4da6746bb988ad7286713879742686d25c and https://github.com/azlux/pymumble/commit/4e4ea1303ae0dfbfd34e09504cf1865a0309fe0a , both commit was to implement new Admin feature to pymumble.

Feel free to ask informations if you need it, open a PR or at least sharing you progress in case you're stuck.

Alex-Programs commented 3 years ago

Thanks! I'll try to implement it.

Alex-Programs commented 3 years ago

So @CaptainZidgel said that the hard part would be sending it on to the server. I'm not familiar with the Mumble protocol, how would I do that? Are there any docs I can use? Looking at the commits I can't actually find where it communicates with the server, unless it was self.send_message(PYMUMBLE_MSG_TYPES_USERREMOVE, userremove)? Thanks for all the help though, I'm going to try to implement it with some trial and error.

CaptainZidgel commented 3 years ago

self.send_message(PYMUMBLE_MSG_TYPES_USERREMOVE, userremove)?

Yes that should be right.... self.send_message(PYMUMBLE_MSG_TYPES_ACL, editedACL) But I was about to type "just follow it up with the thread boiler plate"

cmd.response = True
self.commands.answer(cmd)

but that works from the treat_command method which would mean you'd have to add a command message type in messages.py,

#messages.py
class ModifyACL(Cmd):
   def __init__(self, ACLObject):
        Cmd.__init__(self)

        self.cmd = PYMUMBLE_CMD_MODACL #you would need to add this constant to constants.py
        self.parameters = {'acl': ACLObject}

then add the code to send the message in treat_commands,

def treat_command
###snip###
      elif cmd.cmd == PYMUMBLE_CMD_MODACL:
            acl = cmd.parameters["acl"]
            self.send_message(PYMUMBLE_MSG_TYPES_ACL, acl)
           ########I am not sure if these can be left out but I do not see what they would serve here if the command isn't invoked from another file
           #cmd.response = True
           # self.commands.answer(cmd)

I do not actually know anything about the thread of pymumble and why these answers are necessary... A line here https://github.com/azlux/pymumble/blob/f5f13ea3e160e3a94f3602fbb994d0151778ce90/pymumble_py3/mumble.py#L178 shows a message being sent without needing a response. I do not even know how you would invoke such a command because the commands normally written, in the users.py or channels.py file, inherit from a User or Channel object. This command would not fit under either, but I don't know how you would construct it. I would like to hear azlux's opinion!

azlux commented 3 years ago

The entry point is the message like you said. self.send_message(PYMUMBLE_MSG_TYPES_ACL, editedACL) The Class inside messages.py is just to freeze the cmd (ex:PYMUMBLE_MSG_TYPES_ACL) constant and the attached parameters.

The message is build with Protobuff Mumble file compiled for python3 libraries, look at the name of parameters, there are the sames. So the file mumble.py will build the message to send to the server with parameters specified by the protobuf file.

cmd.response = True is to remove the message, self.commands.answer(cmd) is to release the lock (to avoid sending 2 message in the same time, because it's a thread)

the last question is where we store the additional information. Inside User ? Channel ? On mumble, ACL is channel based, not user. So I suggest you to work with channls.py

I hope I give you the informations you need.

Azlux

azlux commented 3 years ago

The code is in progress, I'm almost at the end. It's longer than expected because I make it ready for futur ACL management.

azlux commented 3 years ago

Hi, the code is done and ready ! See https://github.com/azlux/pymumble/commit/bebeb121816956a75c2d5e5a20c52617708bb481

You can read the doc https://github.com/azlux/pymumble/blob/pymumble_py3/API.md#acl-object . The new ACL object is documented.

If it's working on your side, I will publish the change into next release.

Alex-Programs commented 3 years ago

Thanks! Sorry, I missed your comment. I'll give it a test.

azlux commented 3 years ago

@AlexAndHisScripts and @CaptainZidgel Can I close this issue ? I will release a new pymumble version as soon you validate it's ok.

CaptainZidgel commented 3 years ago

Sorry, I haven't been active with my Mumble project or much CS recently. I would wait on Alex's go ahead since they were the one using this feature. On a cursory glance the code looks fine, but I don't have time to write tests.

azlux commented 3 years ago

Added to the small release https://github.com/azlux/pymumble/releases/tag/1.6.1