CactusDev / CactusBot

An open source, community-written service-agnostic chat bot
MIT License
30 stars 5 forks source link

Custom packet format #43

Closed 2Cubed closed 8 years ago

2Cubed commented 8 years ago

We need a packet format, with support for:

2Cubed commented 8 years ago

@RPiAwesomeness: We had discussed doing this with custom classes, and using __dict__ for future JSON-ification. For simplicity's sake, though, should we consider using plain dictionaries, for now, and then potentially upgrading to custom classes later?

2Cubed commented 8 years ago

For message packets specifically, I like the following format.

[
  {
    "type": "mention",
    "data": "2Cubed",
    "text": "@2Cubed"
  },
  {
    "type": "text",
    "data": "Hi! ",
    "text": "Hi! "
  },
  {
    "type": "emoticon",
    "data": "smile",
    "text": ":)"
  },
  {
    "type": "text",
    "data": " ",
    "text": " "
  },
  {
    "type": "link",
    "data": "https://google.com",
    "text": "google.com"
  }
]
Innectic commented 8 years ago

@2Cubed I like the format of that personally. However, what's the purpose of data AND text in link?

Innectic commented 8 years ago

@2Cubed I just realized, that packet doesn't contain user. Where should that go?

2Cubed commented 8 years ago

@Innectic I think we should ensure that every chunk in the packet has data and text attributes. (This lets us do, for example, ''.join(chunk["text"] for chunk in packet) without having to worry about chunk potentially not having the text key.

In the case of links specifically, we don't want to use different text than exactly what the user sent. (!command add google google.com, !google should produce google.com, with no https://.) However, it may be useful to have "extra stuff" added to the plaintext version, for... Reasons™? I guess the main reason is to adhere to the convention of always having text and data. (And since we're already getting the https:// version from Beam, might as well use it, right? /shrug)

2Cubed commented 8 years ago

@Innectic That's only for part of the message packet. (I should have clarified.) The entire packet will have to be a bit more complex... something like the following, maybe?

{
  "user": "Innectic",
  "role": 50,
  "message": [{
    "type": "mention",
    "data": "2Cubed",
    "text": "@2Cubed"
  }]
}

("role": 50 is referencing a numerical role value. Here, I'm pretending it means Mod on Beam. This is so that we can, for example, use custom configurations to make Discord users with the ExoZone role also 50, giving them the same permissions as a Mod. We need to figure this system out more, too, and write a standard conversion table with meanings, i.e. Mod=50, Owner=100, etc.)

We still need to decide on a complete list of keys, but so far, we definitely need user, role, and message.

Innectic commented 8 years ago

@2Cubed That packet format seems fine to me. I know that @RPiAwesomeness wants to do object-based packets, though.

2Cubed commented 8 years ago

Here's the current idea for a class-based approach.

Packets

class Packet:
    """Basic packet implementation."""
    # To be used for packets that do not require special functionalities.
    # Should be inherited from to provide custom functionalities, as shown below in MessagePacket.
    def __init__(self, *, packet_type=''):
        self.packet_type = packet_type

    def __dict__(self):
        # convert data to a dict, so that we can still store as JSON

class MessagePacket(Packet):
    """Message packet."""
    # Contains helper functions based around message packets.
    # Can be converted entirely to JSON, and will only lose the helper functions.

    def split(self, sep):
        # Split a packet by a character. Same functionality as str.split.

    def __getitem__(self, index):
        # get index-th character of message

    def __add__():
    def __len__():
    # etc.

Parser

class BeamParser(things):  # formerly BeamHandler
    ...
    def parse_message(self, packet):
        result = fancy_beam_message_parse(packet)
        # Parsing should be done here BeamParser - fancy_beam_message_parse for example's sake.
        return MessagePacket(result)

Handlers

class CommandHandler(Handler):
    def on_message(self, packet):
        if packet[0] == '!':  # __getitem__
            command, *args = packet.split()
            # MessagePacket("command"), (MessagePacket("arg1"), MessagePacket("arg2"), etc.)
            requests.post("/commands/"+str(command), dict(args))
RPiAwesomeness commented 8 years ago

__dict__ is already an attribute for class instances, it returns a dict of all the defined attributes (self.foo = "bar", etc.). So, we would have to override that.

Do we want to do this, or will instance.__dict__ suffice? Doing dict(instance) raises TypeError: 'Foo' object is not iterable even when __dict__() is overridden.

Also, __dict__() will have to defined under each packet, since individually the packets will have different structure and we may want to not include certain keys/attributes (possibly).

2Cubed commented 8 years ago

@RPiAwesomeness Oops, my mistake - meant __iter__. (See this StackOverflow answer.)

We would define a general __iter__ in the Packet class, and children (MessagePacket, etc.) can override it for custom functionalities.

We could also use .raw or something, if you think that would be better.

2Cubed commented 8 years ago

Completed in ecf51a077603954ec5e14622ceffbd40970e8d01.