mirror of
https://github.com/jlu5/PyLink.git
synced 2025-01-11 20:52:42 +01:00
Merge remote-tracking branch 'origin/wip/document-everything' into devel
This commit is contained in:
commit
9dc836d921
18
docs/technical/README.md
Normal file
18
docs/technical/README.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
## PyLink Developer Documentation
|
||||||
|
|
||||||
|
Please note that as PyLink is still in its development phase, the API is subject to change.
|
||||||
|
Any documentation here is provided for reference only.
|
||||||
|
|
||||||
|
### Introduction
|
||||||
|
|
||||||
|
PyLink is an a modular, plugin-based IRC PseudoService framework. It uses swappable protocol modules and a hook-based system for calling plugins, allowing them to function regardless of the IRCd used.
|
||||||
|
|
||||||
|
<img src="core-structure.png" width="50%" height="50%">
|
||||||
|
|
||||||
|
### Contents
|
||||||
|
|
||||||
|
- [Writing plugins for PyLink](writing-plugins.md)
|
||||||
|
- [PyLink hooks reference](hooks-reference.md)
|
||||||
|
- [PyLink protocol module specification](pmodule-spec.md)
|
||||||
|
- [Writing tests for PyLink modules](writing-tests.md)
|
||||||
|
- [Using PyLink's utils module](using-utils.md)
|
27
docs/technical/core-structure.dot
Normal file
27
docs/technical/core-structure.dot
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
digraph G {
|
||||||
|
ratio = 1.3;
|
||||||
|
subgraph cluster_core {
|
||||||
|
label="PyLink Application Structure";
|
||||||
|
style="filled";
|
||||||
|
node [style="filled",color="white"];
|
||||||
|
color="lightblue";
|
||||||
|
subgraph cluster_testsuite {
|
||||||
|
label="Test Suite API";
|
||||||
|
style="filled";
|
||||||
|
node [style="filled",color="white"];
|
||||||
|
color=moccasin;
|
||||||
|
"Dummy protocol\nmodule" -> "Dummy IRC\nobject (FakeIRC)" [color=darkgreen];
|
||||||
|
"Dummy IRC\nobject (FakeIRC)" -> "Dummy protocol\nmodule" [color=darkgreen];
|
||||||
|
}
|
||||||
|
|
||||||
|
"IRC object" -> "Protocol module" -> "PyLink hooks" -> Plugins;
|
||||||
|
"Main program" -> "IRC object" [color=indigo] [label="Spawns 1/net"] [fontcolor=indigo];
|
||||||
|
"Main program" -> "Dummy IRC\nobject (FakeIRC)" [color=darkgreen] [label="(test suite runner)"] [fontcolor=darkgreen];
|
||||||
|
}
|
||||||
|
|
||||||
|
"Protocol module" -> "Remote IRCd" -> "Protocol module";
|
||||||
|
Plugins -> "Protocol module" [label="Communicates \nvia*Client/*Server\nfunctions"] [color=navyblue] [fontcolor=navyblue];
|
||||||
|
Plugins -> "Main program" [label="Registers commands\n& hook handlers"] [color=brown] [fontcolor=brown];
|
||||||
|
"Dummy protocol\nmodule" -> "PyLink hooks" [color=darkgreen];
|
||||||
|
|
||||||
|
}
|
BIN
docs/technical/core-structure.png
Normal file
BIN
docs/technical/core-structure.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
58
docs/technical/plugin_example.py
Normal file
58
docs/technical/plugin_example.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# plugin_example.py: An example PyLink plugin.
|
||||||
|
|
||||||
|
# These two lines add PyLink's root directory to the PATH, so that importing things like
|
||||||
|
# 'utils' and 'log' work.
|
||||||
|
import sys, os
|
||||||
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
import utils
|
||||||
|
from log import log
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
|
# Example PRIVMSG hook that returns "hi there!" when PyLink's nick is mentioned
|
||||||
|
# in a channel.
|
||||||
|
|
||||||
|
# irc: The IRC object where the hook was called.
|
||||||
|
# source: The UID/numeric of the sender.
|
||||||
|
# command: The true command name where the hook originates. This may or may not be
|
||||||
|
# the same as the name of the hook, depending on context.
|
||||||
|
# args: The hook data (a dict) associated with the command. The available data
|
||||||
|
# keys differ by hook name (see the hooks reference for a list of which can
|
||||||
|
# be used).
|
||||||
|
def hook_privmsg(irc, source, command, args):
|
||||||
|
channel = args['target']
|
||||||
|
text = args['text']
|
||||||
|
# irc.pseudoclient stores the IrcUser object of the main PyLink client.
|
||||||
|
# (i.e. the user defined in the bot: section of the config)
|
||||||
|
if utils.isChannel(channel) and irc.pseudoclient.nick in text:
|
||||||
|
utils.msg(irc, channel, 'hi there!')
|
||||||
|
log.info('%s said my name on channel %s (PRIVMSG hook caught)' % (source, channel))
|
||||||
|
utils.add_hook(hook_privmsg, 'PRIVMSG')
|
||||||
|
|
||||||
|
|
||||||
|
# Example command function. @utils.add_cmd binds it to an IRC command of the same name,
|
||||||
|
# but you can also use a different name by specifying a second 'name' argument (see below).
|
||||||
|
@utils.add_cmd
|
||||||
|
# irc: The IRC object where the command was called.
|
||||||
|
# source: The UID/numeric of the calling user.
|
||||||
|
# args: A list of command args (excluding the command name) that the command was called with.
|
||||||
|
def randint(irc, source, args):
|
||||||
|
# The docstring here is used as command help by the 'help' command, and formatted using the
|
||||||
|
# same line breaks as the raw string. You shouldn't make this text or any one line too long,
|
||||||
|
# to prevent flooding users or getting long lines cut off.
|
||||||
|
"""[<min>] [<max>]
|
||||||
|
Returns a random number between <min> and <max>. <min> and <max> default
|
||||||
|
to 1 and 10 respectively, if both aren't given."""
|
||||||
|
try:
|
||||||
|
rmin = args[0]
|
||||||
|
rmax = args[1]
|
||||||
|
except IndexError:
|
||||||
|
rmin, rmax = 1, 10
|
||||||
|
n = random.randint(rmin, rmax)
|
||||||
|
utils.msg(irc, source, str(n))
|
||||||
|
# You can also bind a command function multiple times, and/or to different command names via a
|
||||||
|
# second argument.
|
||||||
|
# Note: no checking is done at the moment to prevent multiple plugins from binding to the same
|
||||||
|
# command name. The older command just gets replaced by the new one!
|
||||||
|
utils.add_cmd(randint, "random")
|
49
docs/technical/writing-plugins.md
Normal file
49
docs/technical/writing-plugins.md
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# Writing plugins for PyLink
|
||||||
|
|
||||||
|
PyLink plugins are modules that extend its functionality by giving it something to do. Without any plugins loaded, PyLink can only sit on a server and do absolutely nothing.
|
||||||
|
|
||||||
|
This guide, along with the sample plugin [`plugin-example.py`](plugin-example.py), aim to show the basics of writing plugins for PyLink.
|
||||||
|
|
||||||
|
### Receiving data from IRC
|
||||||
|
|
||||||
|
Plugins have three main ways of communicating with IRC: hooks, WHOIS handlers, and commands sent in PM to the main PyLink client. A simple plugin can use one, or any mixture of these.
|
||||||
|
|
||||||
|
### Hooks
|
||||||
|
|
||||||
|
Hooks are probably the most versatile form of communication. Each hook payload is formatted as a Python `dict`, with different data keys depending on the command.
|
||||||
|
For example, a `PRIVMSG` payload would give you the fields `target` and `text`, while a `PART` payload would only give you `channels` and `reason` fields.
|
||||||
|
|
||||||
|
There are many hook types available (one for each supported IRC command), and you can read more about them in the [PyLink hooks reference](hooks-reference.md).
|
||||||
|
|
||||||
|
Plugins can bind to hooks using the `utils.add_hook()` function like so: `utils.add_hook(function_name, 'PRIVMSG')`, where `function_name` is your function definition, and `PRIVMSG` is whatever hook name you want to bind to. Once set up, `function_name` will be called whenever the protocol module receives a `PRIVMSG` command.
|
||||||
|
|
||||||
|
Each hook-bound function takes 4 arguments: `irc, source, command, args`.
|
||||||
|
- **irc**: The IRC object where the hook was called. Plugins are globally loaded, so there will be one of these per network.
|
||||||
|
- **source**: The numeric of the sender. This will usually be a UID (for users) or a SID (for server).
|
||||||
|
- **command**: The true command name where the hook originates. This may or may not be the same as the name of the hook, depending on context.
|
||||||
|
- **args**: The hook data (a `dict`) associated with the command. Again, the available data keys differ by hook name
|
||||||
|
(see the [hooks reference](hooks-reference.md) for a list of which can be used).
|
||||||
|
|
||||||
|
Hook functions do not return anything, and can raise exceptions to be caught by the core.
|
||||||
|
|
||||||
|
### PyLink commands
|
||||||
|
|
||||||
|
For plugins that interact with IRC users, there is also the option of binding to PM commands.
|
||||||
|
|
||||||
|
Commands are bound to using the `utils.add_cmd()` function: `utils.add_cmd(testcommand, "hello")`. Here, `testcommand` is the name of your function, and `hello` is the (optional) name of the command to bind to; if it is not specified, it'll use the same name as the function.
|
||||||
|
Now, your command function will be called whenever someone PMs the PyLink client with the command (e.g. `/msg PyLink hello`, case-insensitive).
|
||||||
|
|
||||||
|
Each command function takes 3 arguments: `irc, source, args`.
|
||||||
|
- **irc**: The IRC object where the command was called.
|
||||||
|
- **source**: The numeric of the sender. This will usually be a UID (for users) or a SID (for server).
|
||||||
|
- **args**: A `list` of space-separated command args (excluding the command name) that the command was called with. For example, `/msg PyLink hello world 1234` would give an `args` list of `['world', '1234']`
|
||||||
|
|
||||||
|
Command handlers do not return anything, and can raise exceptions to be caught by the core.
|
||||||
|
|
||||||
|
### WHOIS handlers
|
||||||
|
|
||||||
|
The third option, `WHOIS` handlers, are a lot more limited compared to the other options. They are solely used for `WHOIS` replies, **and only work on IRCds where WHOIS commands are sent to remote servers!** This includes Charybdis and UnrealIRCd, but **not** InspIRCd, which handles all `WHOIS` requests locally (the only thing sent between servers is an IDLE time query).
|
||||||
|
|
||||||
|
WHOIS replies are special in that any plugins wishing to add lines to a WHOIS reply must do so after the regular WHOIS lines (handled by the core), but before a special "End of WHOIS" line. This means that the regular hooks mechanism, which are only called after core handling, won't work here.
|
||||||
|
|
||||||
|
\- section under construction -
|
@ -1,19 +0,0 @@
|
|||||||
# hooks.py: test of PyLink hooks
|
|
||||||
import sys, os
|
|
||||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
||||||
import utils
|
|
||||||
from log import log
|
|
||||||
|
|
||||||
def hook_join(irc, source, command, args):
|
|
||||||
channel = args['channel']
|
|
||||||
users = args['users']
|
|
||||||
log.info('%s joined channel %s (JOIN hook caught)' % (users, channel))
|
|
||||||
utils.add_hook(hook_join, 'JOIN')
|
|
||||||
|
|
||||||
def hook_privmsg(irc, source, command, args):
|
|
||||||
channel = args['target']
|
|
||||||
text = args['text']
|
|
||||||
if utils.isChannel(channel) and irc.pseudoclient.nick in text:
|
|
||||||
irc.msg(channel, 'hi there!')
|
|
||||||
log.info('%s said my name on channel %s (PRIVMSG hook caught)' % (source, channel))
|
|
||||||
utils.add_hook(hook_privmsg, 'PRIVMSG')
|
|
Loading…
Reference in New Issue
Block a user