3
0
mirror of https://github.com/jlu5/PyLink.git synced 2024-11-01 09:19:23 +01:00

Refresh protocol spec for 1.0-beta1+

- Replace utils.parseModes() with irc.parseModes()
- Update source code links to 1.0-beta1
This commit is contained in:
James Lu 2017-01-09 22:23:35 -08:00 committed by GitHub
parent 70deb5a285
commit 7fe65e1f8a

View File

@ -1,8 +1,10 @@
# PyLink Protocol Module Specification
***Last updated for 0.10-alpha1 (2016-08-24).***
***Last updated for 1.0-beta1+ (2017-01-09).***
In PyLink, each protocol module is a file consisting of a protocol class (e.g. `InspIRCdProtocol`), and a global `Class` attribute set equal to it (e.g. `Class = InspIRCdProtocol`). These classes are usually based off boilerplate classes such as `classes.Protocol`, `protocols.ircs2s_common.IRCS2SProtocol`, or other protocol module classes that share functionality with it. [[Protocol module inheritence graph]](protocol-modules.png)
In PyLink, each protocol module is a file consisting of a protocol class (e.g. `InspIRCdProtocol`), and a global `Class` attribute set equal to it (e.g. `Class = InspIRCdProtocol`). These classes are usually based off boilerplate classes such as `classes.Protocol`, `protocols.ircs2s_common.IRCS2SProtocol`, or other protocol module classes that share functionality with it.
![[Protocol module inheritence graph]](protocol-modules.png)
IRC objects load protocol modules by creating an instance of this `Class` attribute, and then proceeding to call its commands.
@ -16,13 +18,13 @@ Protocol modules have some very important jobs. If any of these aren't done corr
3) Make sure channel/user states are kept correctly. Joins, quits, parts, kicks, mode changes, nick changes, etc. should all be handled accurately.
4) Respond to both pings *and* pongs - the `irc.lastping` attribute **must** be set to the current time whenever a `PONG` is received from the uplink, so PyLink's doesn't [lag out the uplink thinking that it isn't responding to our pings](https://github.com/GLolol/PyLink/blob/e4fb64aebaf542122c70a8f3a49061386a00b0ca/classes.py#L309-L311).
4) Respond to both pings *and* pongs - the `irc.lastping` attribute **must** be set to the current time whenever a `PONG` is received from the uplink, so PyLink's doesn't [lag out the uplink](https://github.com/GLolol/PyLink/blob/1.0-beta1/classes.py#L383-L386) thinking that it isn't responding to our pings.
5) Implement a series of outgoing command functions (see below), used by plugins to send commands to IRC.
6) Set the threading.Event object `irc.connected` (via `irc.connected.set()`) when the protocol negotiation with the uplink is complete. This is important for plugins like Relay which must check that links are ready before spawning clients, and they will fail to work if this is not set.
7) Check to see that RECVPASS is correct. Always.
7) Check that `recvpass` is correct (when applicable), and raise `ProtocolError` with a relevant error message if not.
## Core functions
@ -30,15 +32,15 @@ The following functions *must* be implemented by any protocol module within its
- **`connect`**`(self)` - Initializes a connection to a server.
- **`handle_events`**`(self, line)` - Handles inbound data (lines of text) from the uplink IRC server. Normally, this will pass commands to other command handlers within the protocol module, while dropping commands that are unrecognized (wildcard handling). But, it's really up to you how to structure your modules. You will want to be able to parse command arguments properly into a list: many protocols send RFC1459-style commands that can be parsed using the [`Protocol.parseArgs()`](https://github.com/GLolol/PyLink/blob/e4fb64aebaf542122c70a8f3a49061386a00b0ca/classes.py#L539) function.
- **`handle_events`**`(self, line)` - Handles inbound data (lines of text) from the uplink IRC server. Normally, this will pass commands to other command handlers within the protocol module, while dropping commands that are unrecognized (wildcard handling). This is type of handling is only a guideline, as it's technically possible to structure event listeners any way you want.
- **`ping`**`(self, source=None, target=None)` - Sends a PING to a target server. Periodic PINGs are sent to our uplink automatically by the [`Irc()`
internals](https://github.com/GLolol/PyLink/blob/0.4.0-dev/classes.py#L267-L272); plugins shouldn't have to use this.
internals](https://github.com/GLolol/PyLink/blob/1.0-beta1/classes.py#L474-L483); plugins shouldn't have to use this.
### Outgoing command functions
- **`spawnClient`**`(self, nick, ident='null', host='null', realhost=None, modes=set(), server=None, ip='0.0.0.0', realname=None, ts=None, opertype=None, manipulatable=False)` - Spawns a client on the PyLink server. No nick collision / valid nickname checks are done by protocol modules, as it is up to plugins to make sure they don't introduce anything invalid.
- `modes` is a set of `(mode char, mode arg)` tuples in the form of [`utils.parseModes()` output](using-utils.md#parseModes).
- `modes` is a list or set of `(mode char, mode arg)` tuples in the [PyLink mode format](#mode-formats).
- `ident` and `host` default to "null", while `realhost` defaults to the same things as `host` if not defined.
- `realname` defaults to the real name specified in the PyLink config, if not given.
- `ts` defaults to the current time if not given.
@ -95,16 +97,17 @@ optional, and defaults to the one we've stored in the channel state if not given
A protocol module should also set the following variables in their protocol class:
- `self.casemapping`: set this to `rfc1459` (default) or `ascii` to determine which case mapping the IRCd uses.
- `self.hook_map`: this is a `dict`, which maps non-standard command names sent by the IRCd to those that PyLink plugins use internally.
- Examples exist in the [UnrealIRCd](https://github.com/GLolol/PyLink/blob/0.10-alpha1/protocols/unreal.py#L24-L27) and [InspIRCd](https://github.com/GLolol/PyLink/blob/0.10-alpha1/protocols/inspircd.py#L25-L28) modules.
- `self.hook_map`: this is a `dict`, which maps non-standard command names sent by the IRCd to those used by [PyLink hooks](hooks-reference.md).
- Examples exist in the [UnrealIRCd](https://github.com/GLolol/PyLink/blob/1.0-beta1/protocols/unreal.py#L24-L27) and [InspIRCd](https://github.com/GLolol/PyLink/blob/1.0-beta1/protocols/inspircd.py#L25-L28) modules.
- `self.cmodes` / `self.umodes`: These are mappings of named IRC modes (e.g. `inviteonly` or `moderated`) to a string list of mode letters, that should be either set during link negotiation or hardcoded into the protocol module. There are also special keys: `*A`, `*B`, `*C`, and `*D`, which **must** be set properly with a list of mode characters for that type of mode.
- Types of modes are defined as follows (from http://www.irc.org/tech_docs/005.html):
- A = Mode that adds or removes a nick or address to a list. Always has a parameter.
- B = Mode that changes a setting and always has a parameter.
- C = Mode that changes a setting and only has a parameter when set.
- D = Mode that changes a setting and never has a parameter.
- An example of mode mapping hardcoding can be found here: https://github.com/GLolol/PyLink/blob/0.10-alpha1/protocols/ts6.py#L259-L311
- If not defined, these will default to modes defined by RFC 1459: https://github.com/GLolol/PyLink/blob/0.10-alpha1/classes.py#L123-L148
- If not defined, these will default to modes defined by RFC 1459: https://github.com/GLolol/PyLink/blob/1.0-beta1/classes.py#L127-L152
- An example of mode mapping hardcoding can be found here: https://github.com/GLolol/PyLink/blob/1.0-beta1/protocols/ts6.py#L259-L311
- You can find a list of supported (named) channel modes [here](channel-modes.csv), and a list of user modes [here](user-modes.csv).
- `self.prefixmodes`: This defines a mapping of prefix modes (+o, +v, etc.) to their respective mode prefix. This will default to `{'o': '@', 'v': '+'}` (the standard op and voice) if not defined.
- Example: `self.prefixmodes = {'o': '@', 'h': '%', 'v': '+'}`
@ -118,21 +121,21 @@ When receiving or sending topics, there is a `topicset` attribute in the IRC cha
Modes are stored a special format in PyLink, different from raw mode strings in order to make them easier to parse. Mode strings can be turned into mode *lists*, which are used to represent mode changes in hooks, and when storing modes internally.
`utils.parseModes(irc, target, split_modestring)` is used to convert mode strings to mode lists, where `irc` is the IRC object, `target` is the channel name or UID the mode is being set on, and `split_modestring` is the string of modes to parse, *split at each space* (meaning that it's really a list).
`irc.parseModes(target, modestring)` is used to convert mode strings to mode lists. `target` is the channel name/UID the mode is being set on, while `modestring` takes either a string or string split by spaces (really a list).
- `utils.parseModes(irc, '#chat', ['+tHIs', '*!*@is.sparta'])` would give:
- `irc.parseModes('#chat', ['+tHIs', '*!*@is.sparta'])` would give:
- `[('+t', None), ('+H', None), ('+I', '*!*@is.sparta'), ('+s', None)]`
Also, `parseModes` will automatically convert prefix mode targets from nicks to UIDs, and drop invalid modes settings.
`parseModes` will also automatically convert prefix mode targets from nicks to UIDs, and drop any duplicate (already set) or invalid (e.g. missing argument) modes.
- `utils.parseModes(irc, '#chat', ['+ol', 'invalidnick'])`:
- `irc.parseModes('#chat', ['+ol invalidnick'])`:
- `[]`
- `utils.parseModes(irc, '#chat', ['+o', 'GLolol'])`:
- `irc.parseModes('#chat', ['+o GLolol'])`:
- `[('+o', '001ZJZW01')]`
Then, a parsed mode list can be applied to channel name or UID using `utils.applyModes(irc, target, parsed_modelist)`.
Then, a parsed mode list can be applied to channel name or UID using `irc.applyModes(target, parsed_modelist)`. **Note**: for protocols that accept or reject mode changes based on TS (i.e. practically every IRCd), you may want to use [`Protocol.updateTS(...)`](https://github.com/GLolol/PyLink/blob/1.0-beta1/classes.py#L1252-L1261) to handle TS changes more efficiently.
Internally, modes are stored in channel and user objects as sets: `(userobj or chanobj).modes`:
Internally, modes are stored in `IrcChannel` and `IrcUser` objects as sets, with the `+` prefixing each mode character omitted. This set is accessed via the `modes` attribute:
```
<+GLolol> PyLink-devel, eval irc.users[source].modes
@ -141,7 +144,7 @@ Internally, modes are stored in channel and user objects as sets: `(userobj or c
<@PyLink-devel> {('n', None), ('t', None)}
```
*With the exception of channel prefix modes* (op, voice, etc.), which are stored as a dict of sets in `chanobj.prefixmodes`:
**Exception**: the owner, admin, op, halfop, and voice channel prefix modes are stored separately as a dict of sets in `IrcChannel.prefixmodes`:
```
<@GLolol> PyLink-devel, eval irc.channels['#chat'].prefixmodes
@ -150,10 +153,8 @@ Internally, modes are stored in channel and user objects as sets: `(userobj or c
When a certain mode (e.g. owner) isn't supported on a network, the key still exists in `prefixmodes` but is simply unused.
You can find a list of supported (named) channel modes [here](channel-modes.csv), and a list of user modes [here](user-modes.csv).
### Configuration key validation
Starting with PyLink 0.10.x, protocol modules can specify which config values within a server block they need in order to work. This is done by adjusting the `self.conf_keys` attribute, usually in the protocol module's `__init__()` method. The default set, defined in [`Classes.Protocol`](https://github.com/GLolol/PyLink/blob/0.10-alpha1/classes.py#L1166-L1168), includes `{'ip', 'port', 'hostname', 'sid', 'sidrange', 'protocol', 'sendpass', 'recvpass'}`. Should any of these keys be missing from a server block, PyLink will bail with a configuration error.
Starting with PyLink 0.10.x, protocol modules can specify which config values within a server block they need in order to work. This is done by adjusting the `self.conf_keys` attribute, usually in the protocol module's `__init__()` method. The default set, defined in [`Classes.Protocol`](https://github.com/GLolol/PyLink/blob/1.0-beta1/classes.py#L1202-L1204), includes `{'ip', 'port', 'hostname', 'sid', 'sidrange', 'protocol', 'sendpass', 'recvpass'}`. Should any of these keys be missing from a server block, PyLink will bail with a configuration error.
One protocol module that tweaks this is [`Clientbot`](https://github.com/GLolol/PyLink/blob/0.10-alpha1/protocols/clientbot.py#L17-18), which removes all options except `ip`, `protocol`, and `port`.
As an example, one protocol module that tweaks this is [`Clientbot`](https://github.com/GLolol/PyLink/blob/1.0-beta1/protocols/clientbot.py#L17-L18), which removes all options except `ip`, `protocol`, and `port`.