mirror of
https://github.com/jlu5/PyLink.git
synced 2025-04-20 23:07:53 +02:00
Compare commits
No commits in common. "master" and "2.1-beta1" have entirely different histories.
@ -1,25 +0,0 @@
|
||||
*.yml
|
||||
*.yaml
|
||||
# Git, CI, etc. config files
|
||||
.*
|
||||
test/
|
||||
|
||||
# Automatically generated by setup.py
|
||||
/__init__.py
|
||||
|
||||
env/
|
||||
build/
|
||||
__pycache__/
|
||||
.idea/
|
||||
*.py[cod]
|
||||
*.bak
|
||||
*~
|
||||
*#
|
||||
*.save*
|
||||
*.db
|
||||
*.pid
|
||||
*.pem
|
||||
.eggs
|
||||
*.egg-info/
|
||||
dist/
|
||||
log/
|
@ -1,30 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Write Docker tags for Drone CI: version-YYMMDD, version, major version, latest
|
||||
|
||||
VERSION="$1"
|
||||
|
||||
if test -z "$VERSION"; then
|
||||
echo "Reading version from VERSION file" >&2
|
||||
VERSION=$(<VERSION)
|
||||
fi
|
||||
|
||||
if [[ "$VERSION" == *"alpha"* || "$VERSION" == *"dev"* ]]; then
|
||||
# This should never trigger if reference based tagging is enabled
|
||||
echo "ERROR: Pushing alpha / dev tags is not supported"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
major_version="$(printf '%s' "$VERSION" | cut -d . -f 1)"
|
||||
|
||||
# Date based tag
|
||||
printf '%s' "$VERSION-$(date +%Y%m%d),"
|
||||
# Program version
|
||||
printf '%s' "$VERSION,"
|
||||
|
||||
if [[ "$VERSION" == *"beta"* ]]; then
|
||||
printf '%s' "$major_version-beta,"
|
||||
printf '%s' "latest-beta"
|
||||
else # Stable or rc build
|
||||
printf '%s' "$major_version,"
|
||||
printf '%s' "latest"
|
||||
fi
|
@ -1,94 +0,0 @@
|
||||
local test(py_version) = {
|
||||
"kind": "pipeline",
|
||||
"type": "docker",
|
||||
"name": "test-" + py_version,
|
||||
"steps": [
|
||||
{
|
||||
"name": "test",
|
||||
"image": "python:" + py_version,
|
||||
"commands": [
|
||||
"git submodule update --recursive --remote --init",
|
||||
"pip install -r requirements-docker.txt",
|
||||
"python3 setup.py install",
|
||||
"python3 -m unittest discover test/ --verbose"
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
local build_docker(py_version) = {
|
||||
"kind": "pipeline",
|
||||
"type": "docker",
|
||||
"name": "build_docker",
|
||||
"steps": [
|
||||
{
|
||||
"name": "set Docker image tags",
|
||||
"image": "bash",
|
||||
"commands": [
|
||||
"bash .drone-write-tags.sh $DRONE_TAG > .tags",
|
||||
"# Will build the following tags:",
|
||||
"cat .tags"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "build Docker image",
|
||||
"image": "plugins/docker",
|
||||
"settings": {
|
||||
"repo": "jlu5/pylink",
|
||||
"username": {
|
||||
"from_secret": "docker_user"
|
||||
},
|
||||
"password": {
|
||||
"from_secret": "docker_token"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"trigger": {
|
||||
"event": [
|
||||
"push"
|
||||
],
|
||||
"branch": ["release"],
|
||||
},
|
||||
"depends_on": ["test-" + py_version]
|
||||
};
|
||||
|
||||
local deploy_pypi(py_version) = {
|
||||
"kind": "pipeline",
|
||||
"type": "docker",
|
||||
"name": "deploy_pypi",
|
||||
"steps": [
|
||||
{
|
||||
"name": "pypi_publish",
|
||||
"image": "plugins/pypi",
|
||||
"settings": {
|
||||
"username": "__token__",
|
||||
"password": {
|
||||
"from_secret": "pypi_token"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"trigger": {
|
||||
"event": [
|
||||
"tag"
|
||||
],
|
||||
"ref": {
|
||||
"exclude": [
|
||||
"refs/tags/*alpha*",
|
||||
"refs/tags/*beta*",
|
||||
"refs/tags/*dev*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"depends_on": ["test-" + py_version]
|
||||
};
|
||||
|
||||
[
|
||||
test("3.7"),
|
||||
test("3.8"),
|
||||
test("3.9"),
|
||||
test("3.10"),
|
||||
deploy_pypi("3.10"),
|
||||
build_docker("3.10"),
|
||||
]
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,9 +3,6 @@
|
||||
!example-*.yml
|
||||
!.*.yml
|
||||
|
||||
# Generated from .drone.jsonnet
|
||||
.drone.yml
|
||||
|
||||
# Automatically generated by setup.py
|
||||
/__init__.py
|
||||
|
||||
|
34
.travis.yml
Normal file
34
.travis.yml
Normal file
@ -0,0 +1,34 @@
|
||||
dist: xenial
|
||||
sudo: false
|
||||
|
||||
language: python
|
||||
python:
|
||||
- '3.5'
|
||||
- '3.6'
|
||||
- '3.7'
|
||||
|
||||
install: python3 setup.py install
|
||||
script:
|
||||
- python3 -m compileall .
|
||||
- python3 -m unittest discover test/ --verbose
|
||||
|
||||
deploy:
|
||||
provider: pypi
|
||||
# Enable this to use test mode
|
||||
# server: https://testpypi.python.org/pypi
|
||||
user:
|
||||
secure: Ql6ihu5MDgWuAvT9NYfriGUYGhHpsqwXfZHWDQT+DfRjOqHo9QT7PnfexeBoe6L6cYUkEnIrnAXKtBXGy6UmyvfrnvBl68877dLVuoC8PfQ4J0ej7TVnCJmT/LwRqFvzZXkeg4CIlJsVJ6pvrPHXQBDPH1rj/rWCucchrofmps8=
|
||||
password:
|
||||
secure: JOHSaZDPCImV/TlQ7hqKLzEvxY4/gpYGlZlOvxgFEd/k/sGk13sva1MfQkOh7Fgjblhk/CHt59wVKXa0VaylRugFQnXb+NYNrxYON0IRVsKON20XaLXg7qsyKCS4ml+7cd2KvM8a6LVO9078yLWAhTZkZ69nLIRZwFbmL5+mep4=
|
||||
|
||||
on:
|
||||
tags: true
|
||||
# Only deploy on tags that don't have -alpha, -beta, etc. suffixes attached
|
||||
condition: $(python3 -c 'import re,os; print(bool(re.match(r"^(\d+\.){2,}\d+$", os.environ.get("TRAVIS_TAG", ""))))') == "True"
|
||||
python: '3.6'
|
||||
|
||||
# Because our __init__.py is generated at build-time!
|
||||
skip_cleanup: true
|
||||
|
||||
notifications:
|
||||
email: false
|
17
Dockerfile
17
Dockerfile
@ -1,17 +0,0 @@
|
||||
FROM python:3-alpine
|
||||
|
||||
RUN adduser -D -H -u 10000 pylink
|
||||
|
||||
VOLUME /pylink
|
||||
|
||||
COPY . /pylink-src
|
||||
|
||||
RUN cd /pylink-src && pip3 install --no-cache-dir -r requirements-docker.txt
|
||||
RUN cd /pylink-src && python3 setup.py install
|
||||
RUN rm -r /pylink-src
|
||||
|
||||
USER pylink
|
||||
WORKDIR /pylink
|
||||
|
||||
# Run in no-PID file mode by default
|
||||
CMD ["pylink", "-n"]
|
93
README.md
93
README.md
@ -1,34 +1,34 @@
|
||||
# PyLink IRC Services
|
||||
|
||||
## END OF LIFE NOTICE: This project is no longer maintained. So long and thanks for all the fish.
|
||||
|
||||
<!--
|
||||
[](https://github.com/PyLink/PyLink/tree/master)
|
||||
[](https://pypi.python.org/pypi/pylinkirc/)
|
||||
[](https://hub.docker.com/r/jlu5/pylink)
|
||||
[](https://www.python.org/downloads/)
|
||||
-->
|
||||
[](https://www.python.org/downloads/)
|
||||
[](LICENSE.MPL2)
|
||||
|
||||
PyLink is an extensible, plugin-based IRC services framework written in Python. It aims to be:
|
||||
|
||||
1) a transparent server-side relayer between IRC networks.
|
||||
1) a replacement for the now-defunct Janus.
|
||||
|
||||
2) a versatile framework for developing IRC services.
|
||||
|
||||
PyLink is licensed under the Mozilla Public License, version 2.0 ([LICENSE.MPL2](LICENSE.MPL2)). The [corresponding documentation](docs/) is licensed under the Creative Attribution-ShareAlike 4.0 International License. ([LICENSE.CC-BY-SA-4.0](LICENSE.CC-BY-SA-4.0))
|
||||
PyLink and any bundled software are licensed under the Mozilla Public License, version 2.0 ([LICENSE.MPL2](LICENSE.MPL2)). The corresponding documentation in the [docs/](docs/) folder is licensed under the Creative Attribution-ShareAlike 4.0 International License. ([LICENSE.CC-BY-SA-4.0](LICENSE.CC-BY-SA-4.0))
|
||||
|
||||
## Getting help
|
||||
## Support the project
|
||||
[](https://www.paypal.me/jlucode)
|
||||
|
||||
## Obtaining support
|
||||
|
||||
**First, MAKE SURE you've read the [FAQ](docs/faq.md)!**
|
||||
|
||||
**When upgrading between major versions, remember to read the [release notes](RELNOTES.md) for any breaking changes!**
|
||||
|
||||
Please report any bugs you find to the [issue tracker](https://github.com/PyLink/PyLink/issues). Pull requests are likewise welcome.
|
||||
Please report any bugs you find to the [issue tracker](https://github.com/jlu5/PyLink/issues). Pull requests are open if you'd like to contribute, though new stuff generally goes to the **devel** branch.
|
||||
|
||||
You can also find support via our IRC channel at `#PyLink @ irc.overdrivenetworks.com`. Ask your questions and be patient for a response.
|
||||
|
||||
## Installation
|
||||
|
||||
### Pre-requisites
|
||||
* Python 3.7 or above - prefer the newest Python 3.x when available
|
||||
* CPython 3.5 or above (other intepreters are untested and unsupported)
|
||||
* A Unix-like operating system: PyLink is actively developed on Linux only, so we cannot guarantee that things will work properly on other systems.
|
||||
|
||||
If you are a developer and want to help make PyLink more portable, patches are welcome.
|
||||
@ -44,40 +44,26 @@ If you are a developer and want to help make PyLink more portable, patches are w
|
||||
* *For Unicode support in Relay*: unidecode (`pip3 install Unidecode`)
|
||||
* *For extended PID file tracking (i.e. removing stale PID files after a crash)*: psutil (`pip3 install psutil`)
|
||||
|
||||
2) Clone the repository: `git clone https://github.com/PyLink/PyLink && cd PyLink`
|
||||
- Previously there was a *devel* branch for testing versions of PyLink - this practice has since been discontinued.
|
||||
2) Clone the repository: `git clone https://github.com/jlu5/PyLink && cd PyLink`
|
||||
|
||||
3) Install PyLink using `python3 setup.py install` (global install) or `python3 setup.py install --user` (local install)
|
||||
3) Pick your branch.
|
||||
* By default you'll be on the **master** branch, which contains the latest stable code. This branch is recommended for production networks that don't require new features or intensive bug fixes as they are developed.
|
||||
* The **devel** branch is where active development goes, and it can be accessed by running `git checkout devel` in your Git tree.
|
||||
|
||||
4) Install PyLink using `python3 setup.py install` (global install) or `python3 setup.py install --user` (local install)
|
||||
* Note: `--user` is a *literal* string; *do not* replace it with your username.
|
||||
* **Whenever you switch branches or update PyLink's sources via `git pull`, you will need to re-run this command for changes to apply!**
|
||||
|
||||
### Installing via Docker
|
||||
|
||||
As of PyLink 3.0 there is a Docker image available on Docker Hub: [jlu5/pylink](https://hub.docker.com/r/jlu5/pylink)
|
||||
|
||||
It supports the following tags:
|
||||
|
||||
- Rolling tags: **`latest`** (latest stable/RC release), **`latest-beta`** (latest beta snapshot)
|
||||
- Pinned to a major branch: e.g. **`3`** (latest 3.x stable release), **`3-beta`** (latest 3.x beta snapshot)
|
||||
- Pinned to a specific version: e.g. **`3.0.0`**
|
||||
|
||||
To use this image you should mount your configuration/DB folder into `/pylink`. **Make sure this directory is writable by UID 10000.**
|
||||
|
||||
```bash
|
||||
$ docker run -v $HOME/pylink:/pylink jlu5/pylink
|
||||
```
|
||||
|
||||
### Installing via PyPI (stable branch only)
|
||||
|
||||
1) Make sure you're running the right pip command: on most distros, pip for Python 3 uses the command `pip3`.
|
||||
|
||||
2) Run `pip3 install pylinkirc` to download and install PyLink. pip will automatically resolve dependencies.
|
||||
|
||||
3) Download or copy https://github.com/PyLink/PyLink/blob/master/example-conf.yml for an example configuration.
|
||||
3) Download or copy https://github.com/jlu5/PyLink/blob/master/example-conf.yml for an example configuration.
|
||||
|
||||
## Configuration
|
||||
|
||||
1) Rename `example-conf.yml` to `pylink.yml` (or a similarly named `.yml` file) and configure your instance there.
|
||||
1) Rename `example-conf.yml` to `pylink.yml` (or a similarly named `.yml` file) and configure your instance there. Note that the configuration format isn't finalized yet - this means that your configuration may break in an update!
|
||||
|
||||
2) Run `pylink` from the command line. PyLink will load its configuration from `pylink.yml` by default, but you can override this by running `pylink` with a config argument (e.g. `pylink mynet.yml`).
|
||||
|
||||
@ -87,37 +73,27 @@ $ docker run -v $HOME/pylink:/pylink jlu5/pylink
|
||||
|
||||
These IRCds (in alphabetical order) are frequently tested and well supported. If any issues occur, please file a bug on the issue tracker.
|
||||
|
||||
* [InspIRCd](http://www.inspircd.org/) (2.0 - 3.x) - module `inspircd`
|
||||
- Set the `target_version` option to `insp3` to target InspIRCd 3.x (default), or `insp20` to target InspIRCd 2.0 (legacy).
|
||||
* [charybdis](https://github.com/charybdis-ircd/charybdis) (3.5+) - module `ts6`
|
||||
- For KLINE support to work, a `shared{}` block should be added for PyLink on all servers.
|
||||
* [InspIRCd](http://www.inspircd.org/) 2.0 - 3.x - module `inspircd`
|
||||
Set the `target_version` option to `insp3` to target InspIRCd 3.x, or `insp20` to target InspIRCd 2.0 (currently the default).
|
||||
- For vHost setting to work, `m_chghost.so` must be loaded. For ident and realname changing support, `m_chgident.so` and `m_chgname.so` must be loaded respectively.
|
||||
- Supported channel, user, and prefix modes are negotiated on connect, but hotloading modules that change these is not supported. After changing module configuration, it is recommended to SQUIT PyLink to force a protocol renegotiation.
|
||||
* [Nefarious IRCu](https://github.com/evilnet/nefarious2) (2.0.0+) - module `p10`
|
||||
- Note: Both account cloaks (user and oper) and hashed IP cloaks are optionally supported (`HOST_HIDING_STYLE` settings 0 to 3). Make sure you configure PyLink to match your IRCd settings.
|
||||
* [UnrealIRCd](https://www.unrealircd.org/) (4.2.x - 5.0.x) - module `unreal`
|
||||
* [UnrealIRCd](https://www.unrealircd.org/) 4.2.x - 5.0.x - module `unreal`
|
||||
- Supported channel, user, and prefix modes are negotiated on connect, but hotloading modules that change these is not supported. After changing module configuration, it is recommended to SQUIT PyLink to force a protocol renegotiation.
|
||||
|
||||
### Extended support
|
||||
|
||||
Support for these IRCds exist, but are not tested as frequently and thoroughly. Bugs should be filed if there are any issues, though they may not always be fixed in a timely fashion.
|
||||
|
||||
* [charybdis](https://github.com/charybdis-ircd/charybdis) (3.5+) - module `ts6`
|
||||
- For KLINE support to work, a `shared{}` block should be added for PyLink on all servers.
|
||||
* [ChatIRCd](http://www.chatlounge.net/software) (1.2.x / git master) - module `ts6`
|
||||
- For KLINE support to work, a `shared{}` block should be added for PyLink on all servers.
|
||||
* [juno-ircd](https://github.com/cooper/juno) (13.x / ava) - module `ts6` (see [configuration example](https://github.com/cooper/juno/blob/master/doc/ts6.md#pylink))
|
||||
* [ngIRCd](https://ngircd.barton.de/) (24+) - module `ngircd`
|
||||
- For GLINEs to propagate, the `AllowRemoteOper` option must be enabled in ngIRCd.
|
||||
- `+` (modeless) channels are not supported, and should be disabled for PyLink to function correctly.
|
||||
- For use with Relay, the `CloakHostModeX` setting will work fine but `CloakHost` and `CloakUserToNick` are *not* supported.
|
||||
|
||||
### Legacy extended support
|
||||
|
||||
Support for these IRCds was added at some point but is no longer actively maintained, either due to inactive upstream development or a perceived lack of interest. We recommend migrating to an IRCd in the above two sections.
|
||||
|
||||
* [beware-ircd](http://ircd.bircd.org/) (1.6.3) - module `p10`
|
||||
- Because bircd disallows BURST after ENDBURST for regular servers, U-lines are required for all PyLink servers. Fortunately, wildcards are supported in U-lines, so you can add something along the lines of `U:<your pylink server>:` and `U:*.relay:` (adjust accordingly for your relay server suffix).
|
||||
- Use `ircd: snircd` as the target IRCd.
|
||||
- Halfops, `sethost` (`+h`), and account-based cloaking (`VHostStyle=1`) are supported. Crypted IPs and static hosts (`VHostStyle` 2 and 3) are NOT.
|
||||
* [ChatIRCd](http://www.chatlounge.net/software) (1.2.x / git master) - module `ts6`
|
||||
- For KLINE support to work, a `shared{}` block should be added for PyLink on all servers.
|
||||
* [Elemental-IRCd](https://github.com/Elemental-IRCd/elemental-ircd) (6.6.x / git master) - module `ts6`
|
||||
- For KLINE support to work, a `shared{}` block should be added for PyLink on all servers.
|
||||
* [IRCd-Hybrid](http://www.ircd-hybrid.org/) (8.2.x / svn trunk) - module `hybrid`
|
||||
@ -128,14 +104,19 @@ Support for these IRCds was added at some point but is no longer actively mainta
|
||||
- On ircd-ratbox, all known IPs of users will be shown in `/whois`, even if the client is e.g. a cloaked relay client. If you're paranoid about this, turn off Relay IP forwarding on the ratbox network(s).
|
||||
- For KLINE support to work, a `shared{}` block should be added for PyLink on all servers.
|
||||
* [IRCu](http://coder-com.undernet.org/) (u2.10.12.16+) - module `p10`
|
||||
- Host changing (changehost, relay) is not supported.
|
||||
- Host changing is not supported.
|
||||
* [juno-ircd](https://github.com/cooper/juno) (13.x / ava) - module `ts6` (see [configuration example](https://github.com/cooper/juno/blob/master/doc/ts6.md#pylink))
|
||||
* [ngIRCd](https://ngircd.barton.de/) (24+) - module `ngircd`
|
||||
- For GLINEs to propagate, the `AllowRemoteOper` option must be enabled in ngIRCd.
|
||||
- `+` (modeless) channels are not supported, and should be disabled for PyLink to function correctly.
|
||||
- For use with Relay, the `CloakHostModeX` setting will work fine but `CloakHost` and `CloakUserToNick` are *not* supported.
|
||||
* [snircd](https://development.quakenet.org/) (1.3.x+) - module `p10`
|
||||
- Outbound host changing (i.e. for the `changehost` plugin) is not supported.
|
||||
- Outbound host changing (i.e. for the `changehost` plugin) is not supported on P10 variants other than Nefarious.
|
||||
|
||||
Other TS6 and P10 variations may work, but are not officially supported.
|
||||
|
||||
### Clientbot
|
||||
|
||||
PyLink supports connecting to IRCds as a relay bot and forwarding users back as virtual clients, similar to Janus' Clientbot. This can be useful if the IRCd a network used isn't supported, or if you want to relay certain channels without fully linking with a network.
|
||||
Since v1.0, PyLink supports connecting to IRCds as a relay bot and forwarding users back, similar to Janus' Clientbot. This can be useful if the IRCd a network used isn't supported, or if you want to relay certain channels without fully linking with a network.
|
||||
|
||||
For Relay to work properly with Clientbot, be sure to load the `relay_clientbot` plugin in conjunction with `relay`.
|
||||
|
||||
Note: **Clientbot links can only be used as a leaf for Relay links - they CANNOT be used to host channels!** This means that Relay does not support having all your networks be Clientbot - in those cases you are better off using a classic relay bot, like [RelayNext for Limnoria](https://github.com/jlu5/SupyPlugins/tree/master/RelayNext).
|
||||
|
372
RELNOTES.md
372
RELNOTES.md
@ -1,115 +1,3 @@
|
||||
# PyLink 3.1.0 (2023-01-03)
|
||||
|
||||
This will be my (**@jlu5**'s) last release.
|
||||
|
||||
Changes since 3.1-beta1:
|
||||
|
||||
### Feature changes
|
||||
|
||||
- Allow loading a custom CA certificate via a ssl_cafile option (#677). Thanks to **@paigeadelethompson** for contributing
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- relay: strip slashes (/) from idents
|
||||
- raw: fix permission check logic
|
||||
|
||||
# PyLink 3.1-beta1 (2021-12-30)
|
||||
|
||||
### Feature changes
|
||||
|
||||
- **PyLink now requires Python >= 3.7**
|
||||
- **protocols/inspircd now defaults to InspIRCd 3.x mode** (`target_version=insp3`)
|
||||
- **Default to system IPv4/IPv6 preference when resolving hostnames.** For existing users, this means that PyLink will most likely **default to IPv6** when resolving hostnames if your server supports it!
|
||||
- You can override this by setting `ipv6: false` in your server config block or using an explicit `bindhost`. Connections made to IPs directly are not affected.
|
||||
- **[SECURITY]** exec and raw plugins are now locked behind config options, to disable running arbitrary code by admins
|
||||
- Implement path configuration for PyLink data files (#659)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Various fixes as detected by pylint, thanks to **@Celelibi** for reporting
|
||||
- ircs2s_common: fix parsing clash if the sender's nick matches an IRC command (e.g. on UnrealIRCd)
|
||||
- ircs2s_common: gracefully handle QUIT messages without a reason (seen on Anope / InspIRCd)
|
||||
- Properly handle EAGAIN in non-blocking sockets
|
||||
- relay: fix "channel not found" errors on LINK when the remote casemapping differs. This mainly affects channels with "|" and other RFC1459 special cases in their name
|
||||
- unreal: bounce attempts to CHGIDENT/HOST/NAME services clients
|
||||
- unreal: fix formatting of outgoing `/kill` (#671)
|
||||
- opercmds: remove double "Killed by" prefixes in the `kill` command
|
||||
|
||||
### Internal improvements
|
||||
|
||||
- Added best-effort tracking of user SSL/TLS status (#169)
|
||||
- Add support for oper notices (GLOBOPS/OPERWALL) (#511)
|
||||
- relay: better ident sanitizing for IRCd-Hybrid
|
||||
- Refactored UID generators to be more concise
|
||||
|
||||
# PyLink 3.0.0 (2020-04-11)
|
||||
|
||||
Changes since 3.0-rc1:
|
||||
|
||||
- Added new install instructions via Docker
|
||||
- global: fix type-safety errors when connected to pylink-discord
|
||||
- Various code cleanup
|
||||
|
||||
For a broader summary of changes since 2.0.x, consult the below release notes for PyLink 3.0-rc1.
|
||||
|
||||
# PyLink 3.0-rc1 (2020-02-22)
|
||||
|
||||
PyLink 3.0 brings PyLink up to date with the latest IRCds (InspIRCd 3, UnrealIRCd 5), and introduces Discord integration via the [pylink-discord](https://github.com/PyLink/pylink-discord) contrib module. It also improves support for Unicode nicks in Relay and Clientbot.
|
||||
|
||||
## Major changes since PyLink 2.0.x
|
||||
|
||||
- **Added support for InspIRCd 3 and UnrealIRCd 5.x**:
|
||||
- To enable InspIRCd 3 link mode (highly recommended) on InspIRCd 3 networks, set the `target_version: insp3` option in the relevant network's `server` block.
|
||||
- For UnrealIRCd 5 support, no changes in PyLink are necessary.
|
||||
- **Updated list of required dependencies: added cachetools, removed ircmatch and expiringdict**
|
||||
- Relay: added an optional dependency on unidecode, to translate Unicode nicks to ASCII on networks not supporting it.
|
||||
- Also, nick normalization is now skipped on protocols where it is not necessary (Clientbot, Discord)
|
||||
- Relay now tracks kill/mode/topic clashes and will stop relaying when it detects such a conflict ([issue#23](https://github.com/jlu5/PyLink/issues/23))
|
||||
- Changehost now supports network-specific configuration ([issue#611](https://github.com/jlu5/PyLink/issues/611)) and listening for services account changes - this allows for consistent account based hostmasks for SASL gateways, etc.
|
||||
- Clientbot: added an option to continuously rejoin channels the bot is not in. [issue#647](https://github.com/jlu5/PyLink/issues/647)
|
||||
- Antispam: added optional quit/part message filtering ([issue#617](https://github.com/jlu5/PyLink/issues/617))
|
||||
|
||||
### API changes
|
||||
- **API Break:** Channel, Server, and User keys may now be type `int` - previously, they were always strings.
|
||||
- **API Break:** PyLink now supports having multiple UIDs mapping to the same nick, as this is common on platforms like Discord.
|
||||
- `nick_to_uid()` has been reworked to optionally return multiple nicks when `multi=True`
|
||||
- Using `nick_to_uid()` without this option will raise a warning if duplicate nicks are present, and may be deprecated in the future.
|
||||
- Added `utils.match_text()`, a general (regex-based) glob matcher to replace `ircmatch` calls ([issue#636](https://github.com/jlu5/PyLink/issues/636)).
|
||||
- Editing hook payloads is now officially supported in plugin hook handlers ([issue#452](https://github.com/jlu5/PyLink/issues/452))
|
||||
- Added new protocol module capabilities:
|
||||
- `can-manage-bot-channels`
|
||||
- `freeform-nicks`
|
||||
- `has-irc-modes`
|
||||
- `virtual-server`
|
||||
- SQUIT hooks now track a list of affected servers (SIDs) in the `affected_servers` field
|
||||
|
||||
This branch was previously known as PyLink 2.1. For more detailed changes between 3.0-rc1 and individual 2.1 snapshots, see their separate changelogs below.
|
||||
|
||||
|
||||
## Changes since PyLink 2.1-beta1
|
||||
|
||||
### Feature changes
|
||||
- Added a Dockerfile for PyLink, thanks to @jameswritescode. Official images will be available on Docker Hub before the final 2.1 release.
|
||||
- unreal: declare support for usermodes +G (censor) and +Z (secureonlymsg)
|
||||
- The `version` command now prints the Python interpreter version to assist with debugging
|
||||
|
||||
### Bug fixes
|
||||
- Fix desync when removing multiple ban modes in one MODE command (regression from 2.1-beta1)
|
||||
- p10: properly ignore ACCOUNT subcommands other than R, M, and U
|
||||
- Fix extraneous lowercasing of the network name in the `$account` exttarget, causing per-network matches to fail if the network name had capital letters
|
||||
- inspircd: negotiate casemapping setting on link for InspIRCd 3. [issue#654](https://github.com/jlu5/PyLink/issues/654)
|
||||
- ircs2s_common: fix crash when failing to extract KILL reason
|
||||
|
||||
### Documentation changes
|
||||
- `relay-quickstart`: describe delinking another network from channels owned by the caller's network
|
||||
- Refreshed mode list documentation
|
||||
|
||||
### Internal improvements
|
||||
- Debug logs for mode parsers and other miscellaneous internals are now less noisy
|
||||
- inspircd: warn when using InspIRCd 2 compat mode on an InspIRCd 3 uplink - some commands like KICK are not translated correctly in this mode
|
||||
- classes: fix `SyntaxWarning: "is" with a literal` on Python 3.8
|
||||
|
||||
|
||||
# PyLink 2.1-beta1 (2019-12-08)
|
||||
|
||||
### Feature changes
|
||||
@ -437,58 +325,58 @@ This release contains all changes from 2.0-alpha3 as well as the following:
|
||||
This release contains all changes from 1.3.0, as well as the following:
|
||||
|
||||
#### New features
|
||||
- **Experimental daemonization support via `pylink -d`**. [issue#187](https://github.com/jlu5/PyLink/issues/187)
|
||||
- New (alpha-quality) `antispam` plugin targeting mass-highlight spam: it supports any combination of kick, ban, quiet (mute), and kill as punishment. [issue#359](https://github.com/jlu5/PyLink/issues/359)
|
||||
- **Experimental daemonization support via `pylink -d`**. [issue#187](https://github.com/GLolol/PyLink/issues/187)
|
||||
- New (alpha-quality) `antispam` plugin targeting mass-highlight spam: it supports any combination of kick, ban, quiet (mute), and kill as punishment. [issue#359](https://github.com/GLolol/PyLink/issues/359)
|
||||
- Clientbot now supports expansions such as `$nick` in autoperform.
|
||||
- Relay now translates STATUSMSG messages (e.g. `@#channel` messages) for target networks instead of passing them on as-is. [issue#570](https://github.com/jlu5/PyLink/issues/570)
|
||||
- Relay now translates STATUSMSG messages (e.g. `@#channel` messages) for target networks instead of passing them on as-is. [issue#570](https://github.com/GLolol/PyLink/issues/570)
|
||||
- Relay endburst delay on InspIRCd networks is now configurable via the `servers::NETNAME::relay_endburst_delay` option.
|
||||
- The servermaps plugin now shows the uplink server name for Clientbot links
|
||||
- Added `--trace / -t` options to the launcher for integration with Python's `trace` module.
|
||||
|
||||
#### Feature changes
|
||||
- **Reverted the commit making SIGHUP shutdown the PyLink daemon**. Now, SIGUSR1 and SIGHUP both trigger a rehash, while SIGTERM triggers a shutdown.
|
||||
- The `raw` command has been split into a new plugin (`plugins/raw.py`) with two permissions: `raw.raw` for Clientbot networks, and `raw.raw.unsupported_network` for other protocols. Using raw commands outside Clientbot is not supported. [issue#565](https://github.com/jlu5/PyLink/issues/565)
|
||||
- The `raw` command has been split into a new plugin (`plugins/raw.py`) with two permissions: `raw.raw` for Clientbot networks, and `raw.raw.unsupported_network` for other protocols. Using raw commands outside Clientbot is not supported. [issue#565](https://github.com/GLolol/PyLink/issues/565)
|
||||
- The servermaps plugin now uses two permissions for `map` and `localmap`: `servermaps.map` and `servermaps.localmap` respectively
|
||||
- `showuser` and `showchan` now consistently report times in UTC
|
||||
|
||||
#### Bug fixes
|
||||
- protocols/clientbot: fix errors when connecting to networks with mixed-case server names (e.g. AfterNET)
|
||||
- relay: fix KeyError when a local client is kicked from a claimed channel. [issue#572](https://github.com/jlu5/PyLink/issues/572)
|
||||
- Fix `irc.parse_modes()` incorrectly mangling modes changes like `+b-b *!*@test.host *!*@test.host` into `+b *!*@test.host`. [issue#573](https://github.com/jlu5/PyLink/issues/573)
|
||||
- relay: fix KeyError when a local client is kicked from a claimed channel. [issue#572](https://github.com/GLolol/PyLink/issues/572)
|
||||
- Fix `irc.parse_modes()` incorrectly mangling modes changes like `+b-b *!*@test.host *!*@test.host` into `+b *!*@test.host`. [issue#573](https://github.com/GLolol/PyLink/issues/573)
|
||||
- automode: fix handling of channels with multiple \#'s in them
|
||||
- launcher: prevent protocol module loading errors (e.g. non-existent protocol module) from blocking the setup of other networks.
|
||||
- This fixes a side-effect which can cause relay to stop functioning (`world.started` is never set)
|
||||
- relay_clientbot: fix `STATUSMSG` (`@#channel`) notices from being relayed to channels that it shouldn't
|
||||
- Fixed various 2.0-alpha2 regressions:
|
||||
- Relay now relays service client messages as PRIVMSG and P10 WALL\* commands as NOTICE
|
||||
- protocols/inspircd: fix supported modules list being corrupted when an indirectly linked server shuts down. [issue#567](https://github.com/jlu5/PyLink/issues/567)
|
||||
- networks: `remote` now properly errors if the target service is not available on a network. [issue#554](https://github.com/jlu5/PyLink/issues/554)
|
||||
- protocols/inspircd: fix supported modules list being corrupted when an indirectly linked server shuts down. [issue#567](https://github.com/GLolol/PyLink/issues/567)
|
||||
- networks: `remote` now properly errors if the target service is not available on a network. [issue#554](https://github.com/GLolol/PyLink/issues/554)
|
||||
- commands: fix `showchan` displaying status prefixes in reverse
|
||||
- stats: route permission error replies to notice instead of PRIVMSG
|
||||
- This prevents "Unknown command" flood loops with services which poll `/stats` on link.
|
||||
- clientbot: fixed sending duplicate JOIN hooks and AWAY status updates. [issue#551](https://github.com/jlu5/PyLink/issues/551)
|
||||
- clientbot: fixed sending duplicate JOIN hooks and AWAY status updates. [issue#551](https://github.com/GLolol/PyLink/issues/551)
|
||||
|
||||
#### Internal improvements
|
||||
- **Reading from sockets now uses select instead of one thread per network.**
|
||||
- This new code uses the Python selectors module, which automatically chooses the fastest polling backend available ([`epoll|kqueue|devpoll > poll > select`](https://github.com/python/cpython/blob/v3.6.5/Lib/selectors.py#L599-L601)).
|
||||
- **API Break: significantly reworked channel handling for service bots**. [issue#265](https://github.com/jlu5/PyLink/issues/265)
|
||||
- **API Break: significantly reworked channel handling for service bots**. [issue#265](https://github.com/GLolol/PyLink/issues/265)
|
||||
- The `ServiceBot.extra_channels` attribute in previous versions is replaced with `ServiceBot.dynamic_channels`, which is accessed indirectly via new functions `ServiceBot.add_persistent_channel()`, `ServiceBot.remove_persistent_channel()`, `ServiceBot.get_persistent_channels()`. This API also replaces `ServiceBot.join()` for most plugins, which now joins channels *non-persistently*.
|
||||
- This API change provides plugins with a way of registering dynamic persistent channels, which are consistently rejoined on kick or kill.
|
||||
- Persistent channels are also "dynamic" in the sense that PyLink service bots will now part channels marked persistent when they become empty, and rejoin when it is recreated.
|
||||
- This new implementation is also plugin specific, as plugins must provide a namespace (usually the plugin name) when managing persistent channels using `ServiceBot.(add|remove)_persistent_channel()`.
|
||||
- New abstraction: `ServiceBot.get_persistent_channels()` which fetches the list of all persistent channels on a network (i.e. *both* the config defined channels and what's registered in `dynamic_channels`).
|
||||
- New abstraction: `ServiceBot.part()` sends a part request to channels and only succeeds if it is not marked persistent by any plugin. This effectively works around the long-standing issue of relay-services conflicts. [issue#265](https://github.com/jlu5/PyLink/issues/265)
|
||||
- New abstraction: `ServiceBot.part()` sends a part request to channels and only succeeds if it is not marked persistent by any plugin. This effectively works around the long-standing issue of relay-services conflicts. [issue#265](https://github.com/GLolol/PyLink/issues/265)
|
||||
- Major optimizations to `irc.nick_to_uid`: `PyLinkNetworkCore.users` and `classes.User` now transparently maintain an index mapping nicks to UIDs instead of doing reverse lookup on every call.
|
||||
- This is done via a new `UserMapping` class in `pylinkirc.classes`, which stores User objects by UID and provides a `bynick` attribute mapping case-normalized nicks to lists of UIDs.
|
||||
- `classes.User.nick` is now a property, where the setter implicitly updates the `bynick` index with a pre-computed case-normalized version of the nick (also stored to `User.lower_nick`)
|
||||
- Various relay optimizations: reuse target SID when bursting joins, and only look up nick once in `normalize_nick`
|
||||
- Rewritten CTCP plugin, now extending to all service bots. [issue#468](https://github.com/jlu5/PyLink/issues/468), [issue#407](https://github.com/jlu5/PyLink/issues/407)
|
||||
- Rewritten CTCP plugin, now extending to all service bots. [issue#468](https://github.com/GLolol/PyLink/issues/468), [issue#407](https://github.com/GLolol/PyLink/issues/407)
|
||||
- Relay no longer spams configured U-lines with "message dropped because you aren't in a common channel" errors
|
||||
- The `endburst_delay` option to `spawn_server()` was removed from the protocol spec, and replaced by a private API used by protocols/inspircd and relay.
|
||||
- New API: hook handlers can now filter messages from lower-priority handlers by returning `False`. [issue#547](https://github.com/jlu5/PyLink/issues/547)
|
||||
- New API: added `irc.get_server_option()` to fetch server-specific config variables and global settings as a fallback. [issue#574](https://github.com/jlu5/PyLink/issues/574)
|
||||
- New API: hook handlers can now filter messages from lower-priority handlers by returning `False`. [issue#547](https://github.com/GLolol/PyLink/issues/547)
|
||||
- New API: added `irc.get_server_option()` to fetch server-specific config variables and global settings as a fallback. [issue#574](https://github.com/GLolol/PyLink/issues/574)
|
||||
- automode: replace assert checks with proper exceptions
|
||||
- Renamed methods in log, utils, conf to snake case. [issue#523](https://github.com/jlu5/PyLink/issues/523)
|
||||
- Renamed methods in log, utils, conf to snake case. [issue#523](https://github.com/GLolol/PyLink/issues/523)
|
||||
- Remove `structures.DeprecatedAttributesObject`; it's vastly inefficient for what it accomplishes
|
||||
- clientbot: removed unreliable pre-/WHO join bursting with `userhost-in-names`
|
||||
- API change: `kick` and `kill` command funcitons now raise `NotImplementedError` when not supported by a protocol
|
||||
@ -498,45 +386,45 @@ This release contains all changes from 1.3.0, as well as the following:
|
||||
This release includes all changes from 1.2.2-dev, plus the following:
|
||||
|
||||
#### New features
|
||||
- relay_clientbot: add support for showing prefix modes in relay text, via a new `$mode_prefix` expansion. [issue#540](https://github.com/jlu5/PyLink/issues/540)
|
||||
- relay_clientbot: add support for showing prefix modes in relay text, via a new `$mode_prefix` expansion. [issue#540](https://github.com/GLolol/PyLink/issues/540)
|
||||
- Added new modedelta feature to Relay:
|
||||
- Modedelta allows specifying a list of (named) modes to only apply on leaf channels, which can be helpful to fight spam if leaf networks don't have adequate spam protection.
|
||||
- relay: added new option `server::<networkname>:relay_forcetag_nicks`, a per-network list of nick globs to always tag when introducing users onto a network. [issue#564](https://github.com/jlu5/PyLink/issues/564)
|
||||
- relay: added new option `server::<networkname>:relay_forcetag_nicks`, a per-network list of nick globs to always tag when introducing users onto a network. [issue#564](https://github.com/GLolol/PyLink/issues/564)
|
||||
- Added support for more channel modes in Relay:
|
||||
* blockcaps: inspircd +B, elemental-ircd +G
|
||||
* exemptchanops: inspircd +X
|
||||
* filter: inspircd +g, unreal extban ~T:block ([issue#557](https://github.com/jlu5/PyLink/issues/557))
|
||||
* filter: inspircd +g, unreal extban ~T:block ([issue#557](https://github.com/GLolol/PyLink/issues/557))
|
||||
* hidequits: nefarious +Q, snircd +u
|
||||
* history: inspircd +H
|
||||
* largebanlist: ts6 +L
|
||||
* noamsg: snircd/nefarious +T
|
||||
* blockhighlight: inspircd +V (extras module)
|
||||
* kicknorejoin: elemental-ircd +J ([issue#559](https://github.com/jlu5/PyLink/issues/559))
|
||||
* kicknorejoin_insp: inspircd +J (with argument; [issue#559](https://github.com/jlu5/PyLink/issues/559))
|
||||
* repeat: elemental-ircd +E ([issue#559](https://github.com/jlu5/PyLink/issues/559))
|
||||
* repeat_insp: inspircd +K (with argument; [issue#559](https://github.com/jlu5/PyLink/issues/559))
|
||||
- Added support for UnrealIRCd extban `~T` in Relay. [issue#557](https://github.com/jlu5/PyLink/issues/557)
|
||||
* kicknorejoin: elemental-ircd +J ([issue#559](https://github.com/GLolol/PyLink/issues/559))
|
||||
* kicknorejoin_insp: inspircd +J (with argument; [issue#559](https://github.com/GLolol/PyLink/issues/559))
|
||||
* repeat: elemental-ircd +E ([issue#559](https://github.com/GLolol/PyLink/issues/559))
|
||||
* repeat_insp: inspircd +K (with argument; [issue#559](https://github.com/GLolol/PyLink/issues/559))
|
||||
- Added support for UnrealIRCd extban `~T` in Relay. [issue#557](https://github.com/GLolol/PyLink/issues/557)
|
||||
- p10: added proper support for STATUSMSG notices (i.e. messages to `@#channel` and the like) via WALLCHOPS/WALLHOPS/WALLVOICES
|
||||
- p10: added outgoing /knock support by sending it as a notice
|
||||
- ts6: added incoming /knock handling
|
||||
- relay: added support for relaying /knock
|
||||
|
||||
#### Backwards incompatible changes
|
||||
- **The ratbox protocol module has been merged into ts6**, with a new `ircd: ratbox` option introduced to declare Ratbox as the target IRCd. [issue#543](https://github.com/jlu5/PyLink/issues/543)
|
||||
- **The ratbox protocol module has been merged into ts6**, with a new `ircd: ratbox` option introduced to declare Ratbox as the target IRCd. [issue#543](https://github.com/GLolol/PyLink/issues/543)
|
||||
|
||||
#### Bug fixes
|
||||
- Fix default permissions not applying on startup (2.0-alpha1 regression). [issue#542](https://github.com/jlu5/PyLink/issues/542)
|
||||
- Fix rejoin-on-kill for the main PyLink bot not working (2.0-alpha1/[94e05a6](https://github.com/jlu5/PyLink/commit/94e05a623314e9b0607de4eb01fab28be2e0c7e1) regression).
|
||||
- Fix default permissions not applying on startup (2.0-alpha1 regression). [issue#542](https://github.com/GLolol/PyLink/issues/542)
|
||||
- Fix rejoin-on-kill for the main PyLink bot not working (2.0-alpha1/[94e05a6](https://github.com/GLolol/PyLink/commit/94e05a623314e9b0607de4eb01fab28be2e0c7e1) regression).
|
||||
- Clientbot fixes:
|
||||
- Fix desyncs caused by incomplete nick collision checking when a user on a Clientbot link changes their nick to match an existing virtual client. [issue#535](https://github.com/jlu5/PyLink/issues/535)
|
||||
- Fix desync involving ghost users when a person leaves a channel, changes their nick, and rejoins. [issue#536](https://github.com/jlu5/PyLink/issues/536)
|
||||
- Fix desyncs caused by incomplete nick collision checking when a user on a Clientbot link changes their nick to match an existing virtual client. [issue#535](https://github.com/GLolol/PyLink/issues/535)
|
||||
- Fix desync involving ghost users when a person leaves a channel, changes their nick, and rejoins. [issue#536](https://github.com/GLolol/PyLink/issues/536)
|
||||
- Treat 0 as "no account" when parsing WHOX responses; this fixes incorrect "X is logged in as 0" output on WHOIS.
|
||||
- protocols/p10: fix the `use_hashed_cloaks` server option not being effective.
|
||||
- Fix long standing issues where relay would sometimes burst users multiple times on connect. [issue#529](https://github.com/jlu5/PyLink/issues/529)
|
||||
- Also fix a regression from 2.0-alpha1 where users would not be joined if the hub link is down ([issue#548](https://github.com/jlu5/PyLink/issues/548))
|
||||
- Fix `$a:account` extbans being dropped by relay (they were being confused with `$a`). [issue#560](https://github.com/jlu5/PyLink/issues/560)
|
||||
- Fix corrupt arguments when mixing the `remote` and `mode` commands. [issue#538](https://github.com/jlu5/PyLink/issues/538)
|
||||
- Fix lingering queue threads when networks disconnect. [issue#558](https://github.com/jlu5/PyLink/issues/558)
|
||||
- Fix long standing issues where relay would sometimes burst users multiple times on connect. [issue#529](https://github.com/GLolol/PyLink/issues/529)
|
||||
- Also fix a regression from 2.0-alpha1 where users would not be joined if the hub link is down ([issue#548](https://github.com/GLolol/PyLink/issues/548))
|
||||
- Fix `$a:account` extbans being dropped by relay (they were being confused with `$a`). [issue#560](https://github.com/GLolol/PyLink/issues/560)
|
||||
- Fix corrupt arguments when mixing the `remote` and `mode` commands. [issue#538](https://github.com/GLolol/PyLink/issues/538)
|
||||
- Fix lingering queue threads when networks disconnect. [issue#558](https://github.com/GLolol/PyLink/issues/558)
|
||||
- The relay and global plugins now better handle empty / poorly formed config blocks.
|
||||
- bots: don't allow `spawnclient` on protocol modules with virtual clients (e.g. clientbot)
|
||||
- bots: fix KeyError when trying to join previously nonexistent channels
|
||||
@ -545,10 +433,10 @@ This release includes all changes from 1.2.2-dev, plus the following:
|
||||
- `Channel.sort_prefixes()` now consistently sorts modes from highest to lowest (i.e. from owner to voice). Also removed workaround code added to deal with the wonkiness of this function.
|
||||
- ircs2s_common: add handling for `nick@servername` messages.
|
||||
- `IRCNetwork` should no longer send multiple disconnect hooks for one disconnection.
|
||||
- protocols/ts6 no longer requires `SAVE` support from the uplink. [issue#545](https://github.com/jlu5/PyLink/issues/545)
|
||||
- protocols/ts6 no longer requires `SAVE` support from the uplink. [issue#545](https://github.com/GLolol/PyLink/issues/545)
|
||||
- ts6, hybrid: miscellaneous cleanup
|
||||
- protocols/inspircd now tracks module (un)loads for `m_chghost.so` and friends. [issue#555](https://github.com/jlu5/PyLink/issues/555)
|
||||
- Clientbot now logs failed attempts in joining channels. [issue#533](https://github.com/jlu5/PyLink/issues/533)
|
||||
- protocols/inspircd now tracks module (un)loads for `m_chghost.so` and friends. [issue#555](https://github.com/GLolol/PyLink/issues/555)
|
||||
- Clientbot now logs failed attempts in joining channels. [issue#533](https://github.com/GLolol/PyLink/issues/533)
|
||||
|
||||
# PyLink 2.0-alpha1 (2017-10-07)
|
||||
The "Eclectic" release. This release includes all changes from 1.2.1, plus the following:
|
||||
@ -593,7 +481,7 @@ The "Eclectic" release. This release includes all changes from 1.2.1, plus the f
|
||||
- Fixed a long standing bug where fantasy responses would relay before a user's original command if the `fantasy` plugin was loaded before `relay`. (Bug #123)
|
||||
|
||||
#### Internal changes
|
||||
- **API Break**: The protocol module layer is completely rewritten, with the `Irc` and `Protocol`-derived classes combining into one. Porting **will** be needed for old protocol modules and plugins targetting 1.x; see the [new (WIP) protocol specification](https://github.com/jlu5/PyLink/blob/devel/docs/technical/pmodule-spec.md) for details.
|
||||
- **API Break**: The protocol module layer is completely rewritten, with the `Irc` and `Protocol`-derived classes combining into one. Porting **will** be needed for old protocol modules and plugins targetting 1.x; see the [new (WIP) protocol specification](https://github.com/GLolol/PyLink/blob/devel/docs/technical/pmodule-spec.md) for details.
|
||||
- **API Break**: Channels are now stored in two linked dictionaries per IRC object: once in `irc._channels`, and again in `irc.channels`. The main difference is that `irc._channels` implicitly creates new channels when accessing them if they didn't previously exist (prefer this for protocol modules), while `irc.channels` does not raises and raises KeyError instead (prefer this for plugins).
|
||||
- **API Break**: Most methods in `utils` and `classes` were renamed from camel case to snake case. `log`, `conf`, and others will be ported too before the final 2.0 release.
|
||||
- **API Break**: IRC protocol modules' server introductions must now use **`post_connect()`** instead of **`connect()`** to prevent name collisions with the base connection handling code.
|
||||
@ -610,7 +498,7 @@ The "Eclectic" release. This release includes all changes from 1.2.1, plus the f
|
||||
# PyLink 1.3.0 (2018-05-08)
|
||||
The 1.3 update focuses on backporting some commonly requested and useful features from the WIP 2.0 branch. This release includes all changes from 1.3-beta1, plus the following:
|
||||
|
||||
- Errors due to missing permissions now log to warning. [issue#593](https://github.com/jlu5/PyLink/issues/593)
|
||||
- Errors due to missing permissions now log to warning. [issue#593](https://github.com/GLolol/PyLink/issues/593)
|
||||
- Documentation updates to advanced-relay-config.md and the FAQ
|
||||
|
||||
# PyLink 1.3-beta1 (2018-04-07)
|
||||
@ -618,14 +506,14 @@ The 1.3 update focuses on backporting some commonly requested and useful feature
|
||||
|
||||
#### New features
|
||||
- **Backported the launcher from 2.0-alpha2**:
|
||||
- Added support for daemonization via the `--daemon/-d` option. [issue#187](https://github.com/jlu5/PyLink/issues/187)
|
||||
- Added support for shutdown/restart/rehash via the command line. [issue#244](https://github.com/jlu5/PyLink/issues/244)
|
||||
- The launcher now detects and removes stale PID files when `psutil` (an optional dependency) is installed, making restarting from crashes a more streamlined process. [issue#512](https://github.com/jlu5/PyLink/issues/512)
|
||||
- Added support for daemonization via the `--daemon/-d` option. [issue#187](https://github.com/GLolol/PyLink/issues/187)
|
||||
- Added support for shutdown/restart/rehash via the command line. [issue#244](https://github.com/GLolol/PyLink/issues/244)
|
||||
- The launcher now detects and removes stale PID files when `psutil` (an optional dependency) is installed, making restarting from crashes a more streamlined process. [issue#512](https://github.com/GLolol/PyLink/issues/512)
|
||||
- PID file checking is now enabled by default, with the `--check-pid/-c` option retained as a no-op option for compatibility with PyLink <= 1.2
|
||||
- Following 2.0 changes, sending SIGUSR1 to the PyLink daemon now triggers a rehash (along with SIGHUP).
|
||||
- Service bot idents, hosts, and realnames can now be configured globally and on a per-network basis. [issue#281](https://github.com/jlu5/PyLink/issues/281)
|
||||
- Relay server suffix is now configurable by network (`servers::<netname>::relay_server_suffix` option). [issue#462](https://github.com/jlu5/PyLink/issues/462)
|
||||
- Login blocks can now be restricted to specific networks, opered users, and hostmasks. [issue#502](https://github.com/jlu5/PyLink/issues/502)
|
||||
- Service bot idents, hosts, and realnames can now be configured globally and on a per-network basis. [issue#281](https://github.com/GLolol/PyLink/issues/281)
|
||||
- Relay server suffix is now configurable by network (`servers::<netname>::relay_server_suffix` option). [issue#462](https://github.com/GLolol/PyLink/issues/462)
|
||||
- Login blocks can now be restricted to specific networks, opered users, and hostmasks. [issue#502](https://github.com/GLolol/PyLink/issues/502)
|
||||
- Relay now supports relaying more channel modes, including inspircd blockhighlight +V and exemptchanops +X (the whitelist was synced with 2.0-alpha3)
|
||||
|
||||
#### Bug fixes
|
||||
@ -637,7 +525,7 @@ The 1.3 update focuses on backporting some commonly requested and useful feature
|
||||
- global: ignore empty `global:` configuration blocks
|
||||
|
||||
#### Misc changes
|
||||
- Config loading now uses `yaml.safe_load()` instead of `yaml.load()` so that arbitrary code cannot be executed. [issue#589](https://github.com/jlu5/PyLink/issues/589)
|
||||
- Config loading now uses `yaml.safe_load()` instead of `yaml.load()` so that arbitrary code cannot be executed. [issue#589](https://github.com/GLolol/PyLink/issues/589)
|
||||
- Significantly revised example-conf for wording and consistency.
|
||||
- protocols/unreal: bumped protocol version to 4017 (no changes needed)
|
||||
|
||||
@ -649,7 +537,7 @@ The "Dancer" release. Changes from 1.2.0:
|
||||
- Fix wrong database and PID filenames if the config file name includes a period (".")
|
||||
- automode: don't send empty mode lines if no users match the ACL
|
||||
- networks: check in "remote" that the remote network is actually connected
|
||||
- Fix commonly reported crashes on `logging:` config syntax errors ([49136d5](https://github.com/jlu5/PyLink/commit/49136d5abd609fd5e3ba2ec2e42a0443118e62ab))
|
||||
- Fix commonly reported crashes on `logging:` config syntax errors ([49136d5](https://github.com/GLolol/PyLink/commit/49136d5abd609fd5e3ba2ec2e42a0443118e62ab))
|
||||
- Backported fixes from 2.0-dev:
|
||||
- p10: fix wrong hook name for user introduction
|
||||
- clientbot: warn when an outgoing message is blocked (e.g. due to bans) (#497)
|
||||
@ -719,21 +607,21 @@ For a full list of changes since 1.1.x, consult the changelogs for the 1.2.x bet
|
||||
The "Dynamo" release. This release includes all fixes from 1.1.2, plus the following:
|
||||
|
||||
#### Feature changes
|
||||
- Added configurable encoding support via the `encoding` option in server config blocks ([#467](https://github.com/jlu5/PyLink/pull/467)).
|
||||
- Added configurable encoding support via the `encoding` option in server config blocks ([#467](https://github.com/GLolol/PyLink/pull/467)).
|
||||
- **Certain configuration options were renamed / deprecated:**
|
||||
- The `bot:` configuration block was renamed to `pylink:`, with the old name now deprecated.
|
||||
- `logging:stdout` is now `logging:console` (the previous name was a misnomer since text actually went to `stderr`).
|
||||
- The `bot:prefix` option is deprecated: you should instead define the `prefixes` setting in a separate config block for each service you wish to customize (e.g. set `automode:prefix` and `games:prefix`)
|
||||
- Added new `$and` and `$network` exttargets - see the new [exttargets documentation page](https://github.com/jlu5/PyLink/blob/1.2-beta1/docs/exttargets.md) for how to use them.
|
||||
- Added new `$and` and `$network` exttargets - see the new [exttargets documentation page](https://github.com/GLolol/PyLink/blob/1.2-beta1/docs/exttargets.md) for how to use them.
|
||||
- Hostmasks can now be negated in ban matching: e.g. `!*!*@localhost` now works. Previously, this negation was limited to exttargets only.
|
||||
- `relay_clientbot` no longer colours network names by default. It is still possible to restore the old behaviour by defining [custom clientbot styles](https://github.com/jlu5/PyLink/blob/1.2-beta1/docs/advanced-relay-config.md#custom-clientbot-styles).
|
||||
- `relay_clientbot` no longer colours network names by default. It is still possible to restore the old behaviour by defining [custom clientbot styles](https://github.com/GLolol/PyLink/blob/1.2-beta1/docs/advanced-relay-config.md#custom-clientbot-styles).
|
||||
- `relay_clientbot` no longer uses dark blue as a random colour choice, as it is difficult to read on clients with dark backgrounds.
|
||||
|
||||
#### Bug fixes
|
||||
- Fix service respawn on KILL not working at all - this was likely broken for a while but never noticed...
|
||||
- Fix kick-on-rejoin not working on P10 IRCds when `joinmodes` is set. (a.k.a. acknowledge incoming KICKs with a PART per [the P10 specification](https://github.com/evilnet/nefarious2/blob/ed12d64/doc/p10.txt#L611-L618))
|
||||
- servprotect: only track kills and saves to PyLink clients, not all kills on a network!
|
||||
- Fix `~#channel` prefix messages not working over relay on RFC1459-casemapping networks ([#464](https://github.com/jlu5/PyLink/issues/464)).
|
||||
- Fix `~#channel` prefix messages not working over relay on RFC1459-casemapping networks ([#464](https://github.com/GLolol/PyLink/issues/464)).
|
||||
- Show errors when trying to use `showchan` on a secret channel in the same way as actually non-existent channels. Previously this error response forced replies as a private notice, potentially leaking the existence of secret/private channels.
|
||||
- example-conf: fix reversed description for the password encryption setting.
|
||||
|
||||
@ -913,7 +801,7 @@ The "Crunchy" release. This release includes all bug fixes from PyLink 1.0.4, al
|
||||
- Documentation updates: add a permissions reference, document advanced relay config, etc.
|
||||
|
||||
# PyLink 1.0.4
|
||||
Tagged as **1.0.4** by [jlu5](https://github.com/jlu5)
|
||||
Tagged as **1.0.4** by [GLolol](https://github.com/GLolol)
|
||||
|
||||
The "Bonfire" release.
|
||||
|
||||
@ -987,8 +875,8 @@ The "Candescent" release.
|
||||
- exec: Drop `raw` text logging to DEBUG to prevent information leakage (e.g. passwords on Clientbot)
|
||||
- Removed `update.sh` (my convenience script for locally building + running PyLink)
|
||||
|
||||
# [PyLink 1.0.3](https://github.com/jlu5/PyLink/releases/tag/1.0.3)
|
||||
Tagged as **1.0.3** by [jlu5](https://github.com/jlu5) on 2016-11-20T04:51:11Z
|
||||
# [PyLink 1.0.3](https://github.com/GLolol/PyLink/releases/tag/1.0.3)
|
||||
Tagged as **1.0.3** by [GLolol](https://github.com/GLolol) on 2016-11-20T04:51:11Z
|
||||
|
||||
The "Buoyant" release.
|
||||
|
||||
@ -1006,8 +894,8 @@ The "Buoyant" release.
|
||||
#### Misc. changes
|
||||
- Various spelling/grammar fixes in the example config.
|
||||
|
||||
# [PyLink 1.0.2](https://github.com/jlu5/PyLink/releases/tag/1.0.2)
|
||||
Tagged as **1.0.2** by [jlu5](https://github.com/jlu5) on 2016-10-15T05:52:37Z
|
||||
# [PyLink 1.0.2](https://github.com/GLolol/PyLink/releases/tag/1.0.2)
|
||||
Tagged as **1.0.2** by [GLolol](https://github.com/GLolol) on 2016-10-15T05:52:37Z
|
||||
|
||||
The "Baluga" release.
|
||||
|
||||
@ -1023,8 +911,8 @@ The "Baluga" release.
|
||||
#### Internal fixes / improvements
|
||||
- setup.py: reworded warnings if `git describe --tags` fails / fallback version is used. Also, the internal VCS version for non-Git builds is now `-nogit` instead of `-dirty`.
|
||||
|
||||
# [PyLink 1.0.1](https://github.com/jlu5/PyLink/releases/tag/1.0.1)
|
||||
Tagged as **1.0.1** by [jlu5](https://github.com/jlu5) on 2016-10-06T02:13:42Z
|
||||
# [PyLink 1.0.1](https://github.com/GLolol/PyLink/releases/tag/1.0.1)
|
||||
Tagged as **1.0.1** by [GLolol](https://github.com/GLolol) on 2016-10-06T02:13:42Z
|
||||
|
||||
The "Beam" release.
|
||||
|
||||
@ -1037,8 +925,8 @@ The "Beam" release.
|
||||
- relay: clobber colour codes in hosts
|
||||
- bots: allow JOIN/NICK/QUIT on ServiceBot clients
|
||||
|
||||
# [PyLink 1.0.0](https://github.com/jlu5/PyLink/releases/tag/1.0.0)
|
||||
Tagged as **1.0.0** by [jlu5](https://github.com/jlu5) on 2016-09-17T05:25:51Z
|
||||
# [PyLink 1.0.0](https://github.com/GLolol/PyLink/releases/tag/1.0.0)
|
||||
Tagged as **1.0.0** by [GLolol](https://github.com/GLolol) on 2016-09-17T05:25:51Z
|
||||
|
||||
The "Benevolence" release.
|
||||
|
||||
@ -1066,8 +954,8 @@ The "Benevolence" release.
|
||||
- Added a debug log example <sup><sup><sup>because nobody knew how to turn it on</sup></sup></sup>
|
||||
- Fix inverted option description for Relay's `show_netsplits` option.
|
||||
|
||||
# [PyLink 1.0-beta1](https://github.com/jlu5/PyLink/releases/tag/1.0-beta1)
|
||||
Tagged as **1.0-beta1** by [jlu5](https://github.com/jlu5) on 2016-09-03T07:49:12Z
|
||||
# [PyLink 1.0-beta1](https://github.com/GLolol/PyLink/releases/tag/1.0-beta1)
|
||||
Tagged as **1.0-beta1** by [GLolol](https://github.com/GLolol) on 2016-09-03T07:49:12Z
|
||||
|
||||
The "Badgers" release. Note: This is an **beta** build and may not be completely stable!
|
||||
|
||||
@ -1082,7 +970,7 @@ The "Badgers" release. Note: This is an **beta** build and may not be completely
|
||||
|
||||
#### Feature changes
|
||||
- Irc: implement basic message queueing (1 message sent per X seconds, where X defaults to 0.01 for servers) .
|
||||
- This appears to also workaround sporadic SSL errors causing disconnects (https://github.com/jlu5/PyLink/issues/246)
|
||||
- This appears to also workaround sporadic SSL errors causing disconnects (https://github.com/GLolol/PyLink/issues/246)
|
||||
- relay: CLAIM is now more resistant to things like `/OJOIN` abuse<sup><sup><sup>Seriously people, show some respect for your linked networks ;)</sup></sup></sup>.
|
||||
- core: New permissions system, used exclusively by Automode at this time. See `example-permissions.yml` in the Git tree for configuration options.
|
||||
- relay_clientbot now optionally supports PMs with users linked via Clientbot. This can be enabled via the `relay::allow_clientbot_pms` option, and provides the following behaviour:
|
||||
@ -1104,8 +992,8 @@ The "Badgers" release. Note: This is an **beta** build and may not be completely
|
||||
#### Misc. changes
|
||||
- Various to documentation update and installation instruction improvements.
|
||||
|
||||
# [PyLink 0.10-alpha1](https://github.com/jlu5/PyLink/releases/tag/0.10-alpha1)
|
||||
Tagged as **0.10-alpha1** by [jlu5](https://github.com/jlu5) on 2016-08-22T00:04:34Z
|
||||
# [PyLink 0.10-alpha1](https://github.com/GLolol/PyLink/releases/tag/0.10-alpha1)
|
||||
Tagged as **0.10-alpha1** by [GLolol](https://github.com/GLolol) on 2016-08-22T00:04:34Z
|
||||
|
||||
The "Balloons" release. Note: This is an **alpha** build and may not be completely stable! This version includes all fixes from PyLink 0.9.2, with the following additions:
|
||||
|
||||
@ -1149,8 +1037,8 @@ The "Balloons" release. Note: This is an **alpha** build and may not be complete
|
||||
#### Misc. changes
|
||||
- `FakeIRC` and `FakeProto` are removed (unused and not updated for 0.10 internal APIs)
|
||||
|
||||
# [PyLink 0.9.2](https://github.com/jlu5/PyLink/releases/tag/0.9.2)
|
||||
Tagged as **0.9.2** by [jlu5](https://github.com/jlu5) on 2016-08-21T23:59:23Z
|
||||
# [PyLink 0.9.2](https://github.com/GLolol/PyLink/releases/tag/0.9.2)
|
||||
Tagged as **0.9.2** by [GLolol](https://github.com/GLolol) on 2016-08-21T23:59:23Z
|
||||
|
||||
The "Acorn" release.
|
||||
|
||||
@ -1163,8 +1051,8 @@ The "Acorn" release.
|
||||
- Relay now normalizes `/` to `.` in hostnames on IRCd-Hybrid.
|
||||
- Cloaked hosts for UnrealIRCd 3.2 users are now applied instead of the real host being visible.
|
||||
|
||||
# [PyLink 0.9.1](https://github.com/jlu5/PyLink/releases/tag/0.9.1)
|
||||
Tagged as **0.9.1** by [jlu5](https://github.com/jlu5) on 2016-08-07T03:05:01Z
|
||||
# [PyLink 0.9.1](https://github.com/GLolol/PyLink/releases/tag/0.9.1)
|
||||
Tagged as **0.9.1** by [GLolol](https://github.com/GLolol) on 2016-08-07T03:05:01Z
|
||||
|
||||
### *Important*, backwards incompatible changes for those upgrading from 0.8.x!
|
||||
- The configuration file is now **pylink.yml** by default, instead of **config.yml**.
|
||||
@ -1194,8 +1082,8 @@ Tagged as **0.9.1** by [jlu5](https://github.com/jlu5) on 2016-08-07T03:05:01Z
|
||||
#### Misc. changes
|
||||
- Minor example configuration updates, including a mention of passwordless UnrealIRCd links by setting recvpass and sendpass to `*`.
|
||||
|
||||
# [PyLink 0.9.0](https://github.com/jlu5/PyLink/releases/tag/0.9.0)
|
||||
Tagged as **0.9.0** by [jlu5](https://github.com/jlu5) on 2016-07-25T05:49:55Z
|
||||
# [PyLink 0.9.0](https://github.com/GLolol/PyLink/releases/tag/0.9.0)
|
||||
Tagged as **0.9.0** by [GLolol](https://github.com/GLolol) on 2016-07-25T05:49:55Z
|
||||
|
||||
### *Important*, backwards incompatible changes for those upgrading from 0.8.x!
|
||||
- The configuration file is now **pylink.yml** by default, instead of **config.yml**.
|
||||
@ -1226,8 +1114,8 @@ Tagged as **0.9.0** by [jlu5](https://github.com/jlu5) on 2016-07-25T05:49:55Z
|
||||
- Redone version handling so `__init__.py` isn't committed anymore.
|
||||
- `update.sh` now passes arguments to the `pylink` launcher.
|
||||
|
||||
# [PyLink 0.9-beta1](https://github.com/jlu5/PyLink/releases/tag/0.9-beta1)
|
||||
Tagged as **0.9-beta1** by [jlu5](https://github.com/jlu5) on 2016-07-14T02:11:07Z
|
||||
# [PyLink 0.9-beta1](https://github.com/GLolol/PyLink/releases/tag/0.9-beta1)
|
||||
Tagged as **0.9-beta1** by [GLolol](https://github.com/GLolol) on 2016-07-14T02:11:07Z
|
||||
|
||||
### *Important*, backwards incompatible changes for those upgrading from 0.8.x
|
||||
- The configuration file is now **pylink.yml** by default, instead of **config.yml**.
|
||||
@ -1265,8 +1153,8 @@ Tagged as **0.9-beta1** by [jlu5](https://github.com/jlu5) on 2016-07-14T02:11:0
|
||||
- Relay now creates relay clones with the current time as nick TS, instead of the origin user's TS.
|
||||
- This has the effect of purposely losing nick collisions against local users, so that it's easier to reclaim nicks.
|
||||
|
||||
# [PyLink 0.9-alpha1](https://github.com/jlu5/PyLink/releases/tag/0.9-alpha1)
|
||||
Tagged as **0.9-alpha1** by [jlu5](https://github.com/jlu5) on 2016-07-09T07:27:47Z
|
||||
# [PyLink 0.9-alpha1](https://github.com/GLolol/PyLink/releases/tag/0.9-alpha1)
|
||||
Tagged as **0.9-alpha1** by [GLolol](https://github.com/GLolol) on 2016-07-09T07:27:47Z
|
||||
|
||||
### Summary of changes from 0.8.x
|
||||
|
||||
@ -1277,7 +1165,7 @@ Tagged as **0.9-alpha1** by [jlu5](https://github.com/jlu5) on 2016-07-09T07:27:
|
||||
##### Added / changed / removed features
|
||||
- New **`ctcp`** plugin, handling CTCP VERSION and PING ~~(and perhaps an easter egg?!)~~
|
||||
- New **`automode`** plugin, implementing basic channel ACL by assigning prefix modes like `+o` to hostmasks and exttargets.
|
||||
- New exttarget support: see https://github.com/jlu5/PyLink/blob/0.9-alpha1/coremods/exttargets.py#L15 for a list of supported ones.
|
||||
- New exttarget support: see https://github.com/GLolol/PyLink/blob/0.9-alpha1/coremods/exttargets.py#L15 for a list of supported ones.
|
||||
- Relay can now handle messages sent by users not in a target channel (e.g. for channels marked `-n`)
|
||||
- Relay subserver spawning is now always on - the `spawn_servers` option is removed
|
||||
- Relay can now optionally show netsplits from remote networks, using a `show_netsplits` option in the `relay:` block
|
||||
@ -1315,8 +1203,8 @@ Tagged as **0.9-alpha1** by [jlu5](https://github.com/jlu5) on 2016-07-09T07:27:
|
||||
- protocols/nefarious,ts6,unreal: KILL handling (inbound & outbound) now supports kill paths and formats kill reasons properly
|
||||
- protocols: encapsulated (ENCAP) commands are now implicitly expanded, so protocol modules no longer need to bother with IF statement chains in a `handle_encap()`
|
||||
|
||||
# [PyLink 0.8-alpha4](https://github.com/jlu5/PyLink/releases/tag/0.8-alpha4)
|
||||
Tagged as **0.8-alpha4** by [jlu5](https://github.com/jlu5) on 2016-06-30T18:56:42Z
|
||||
# [PyLink 0.8-alpha4](https://github.com/GLolol/PyLink/releases/tag/0.8-alpha4)
|
||||
Tagged as **0.8-alpha4** by [GLolol](https://github.com/GLolol) on 2016-06-30T18:56:42Z
|
||||
|
||||
Major changes in this snapshot release:
|
||||
|
||||
@ -1331,10 +1219,10 @@ Major changes in this snapshot release:
|
||||
- Example conf: fix various typos (0edb516, cd4bf55) and be more clear about link blocks only being examples
|
||||
- Various freezes and crash bugs fixed (dd08c01, 1ad8b2e, 504a9be, 5f2da1c)
|
||||
|
||||
Full diff: https://github.com/jlu5/PyLink/compare/0.8-alpha3...0.8-alpha4
|
||||
Full diff: https://github.com/GLolol/PyLink/compare/0.8-alpha3...0.8-alpha4
|
||||
|
||||
# [PyLink 0.8-alpha3](https://github.com/jlu5/PyLink/releases/tag/0.8-alpha3)
|
||||
Tagged as **0.8-alpha3** by [jlu5](https://github.com/jlu5) on 2016-06-01T02:58:49Z
|
||||
# [PyLink 0.8-alpha3](https://github.com/GLolol/PyLink/releases/tag/0.8-alpha3)
|
||||
Tagged as **0.8-alpha3** by [GLolol](https://github.com/GLolol) on 2016-06-01T02:58:49Z
|
||||
|
||||
- relay: support relaying a few more channel modes (flood, joinflood, freetarget, noforwards, and noinvite)
|
||||
- Introduce a new (WIP) API to create simple service bots (#216).
|
||||
@ -1345,8 +1233,8 @@ Tagged as **0.8-alpha3** by [jlu5](https://github.com/jlu5) on 2016-06-01T02:58:
|
||||
- New `games` plugin, currently implementing eightball, dice, and fml.
|
||||
- Various fixes to the Nefarious protocol module (89ed92b46a4376abf69698b76955fec010a230b4...c82cc9d822ad46f441de3f2f820d5203b6e70516, #209, #210).
|
||||
|
||||
# [PyLink 0.8-alpha2](https://github.com/jlu5/PyLink/releases/tag/0.8-alpha2)
|
||||
Tagged as **0.8-alpha2** by [jlu5](https://github.com/jlu5) on 2016-05-08T04:40:17Z
|
||||
# [PyLink 0.8-alpha2](https://github.com/GLolol/PyLink/releases/tag/0.8-alpha2)
|
||||
Tagged as **0.8-alpha2** by [GLolol](https://github.com/GLolol) on 2016-05-08T04:40:17Z
|
||||
|
||||
- protocols/nefarious: fix incorrect decoding of IPv6 addresses (0e0d96e)
|
||||
- protocols/(hybrid|nefarious): add missing BURST/SJOIN->JOIN hook mappings, fixing problems with relay missing users after a netjoin
|
||||
@ -1356,10 +1244,10 @@ Tagged as **0.8-alpha2** by [jlu5](https://github.com/jlu5) on 2016-05-08T04:40:
|
||||
- relay: Fix various race conditions, especially when multiple networks happen to lose connection simultaneously
|
||||
- API changes: many commands from `utils` were split into either `Irc()` or a new `structures` module (#199)
|
||||
|
||||
[Full diff](https://github.com/jlu5/PyLink/compare/0.8-alpha1...0.8-alpha2)
|
||||
[Full diff](https://github.com/GLolol/PyLink/compare/0.8-alpha1...0.8-alpha2)
|
||||
|
||||
# [PyLink 0.8-alpha1](https://github.com/jlu5/PyLink/releases/tag/0.8-alpha1)
|
||||
Tagged as **0.8-alpha1** by [jlu5](https://github.com/jlu5) on 2016-04-23T03:14:21Z
|
||||
# [PyLink 0.8-alpha1](https://github.com/GLolol/PyLink/releases/tag/0.8-alpha1)
|
||||
Tagged as **0.8-alpha1** by [GLolol](https://github.com/GLolol) on 2016-04-23T03:14:21Z
|
||||
|
||||
- New protocol support: IRCd-Hybrid 8.x and Nefarious IRCu
|
||||
- Track user IPs of UnrealIRCd 3.2 users (#196)
|
||||
@ -1367,10 +1255,10 @@ Tagged as **0.8-alpha1** by [jlu5](https://github.com/jlu5) on 2016-04-23T03:14:
|
||||
- Improved mode support for Charybdis (#203)
|
||||
- Fix disconnect logic during ping timeouts
|
||||
|
||||
[Full diff](https://github.com/jlu5/PyLink/compare/0.7.2-dev...0.8-alpha1)
|
||||
[Full diff](https://github.com/GLolol/PyLink/compare/0.7.2-dev...0.8-alpha1)
|
||||
|
||||
# [PyLink 0.7.2-dev](https://github.com/jlu5/PyLink/releases/tag/0.7.2-dev)
|
||||
Tagged as **0.7.2-dev** by [jlu5](https://github.com/jlu5) on 2016-04-19T14:03:50Z
|
||||
# [PyLink 0.7.2-dev](https://github.com/GLolol/PyLink/releases/tag/0.7.2-dev)
|
||||
Tagged as **0.7.2-dev** by [GLolol](https://github.com/GLolol) on 2016-04-19T14:03:50Z
|
||||
|
||||
Bug fix release:
|
||||
|
||||
@ -1464,8 +1352,8 @@ Bug fix release:
|
||||
- 1d4350c4fd00e7f8012781992ab73a1b73f396d2 classes: provide IrcChannel objects with their own name using KeyedDefaultdict
|
||||
- 544d6e10418165415c8ffe2b5fbe59fcffd65b0f utils: add KeyedDefaultdict
|
||||
|
||||
# [PyLink 0.7.1-dev](https://github.com/jlu5/PyLink/releases/tag/0.7.1-dev)
|
||||
Tagged as **0.7.1-dev** by [jlu5](https://github.com/jlu5) on 2016-03-31T01:42:41Z
|
||||
# [PyLink 0.7.1-dev](https://github.com/GLolol/PyLink/releases/tag/0.7.1-dev)
|
||||
Tagged as **0.7.1-dev** by [GLolol](https://github.com/GLolol) on 2016-03-31T01:42:41Z
|
||||
|
||||
Bugfix release. Lingering errata which you may still encounter: #183.
|
||||
|
||||
@ -1478,8 +1366,8 @@ Bugfix release. Lingering errata which you may still encounter: #183.
|
||||
- 9cd1635f68dafee47f147de43b258014d14da6e2 unreal: fix wrong variable name in handle_umode2
|
||||
- 2169a9be28331c6207865d50912cd671ff3c34a2 utils: actually abort when mode target is invalid
|
||||
|
||||
# [PyLink 0.7.0-dev](https://github.com/jlu5/PyLink/releases/tag/0.7.0-dev)
|
||||
Tagged as **0.7.0-dev** by [jlu5](https://github.com/jlu5) on 2016-03-21T19:09:12Z
|
||||
# [PyLink 0.7.0-dev](https://github.com/GLolol/PyLink/releases/tag/0.7.0-dev)
|
||||
Tagged as **0.7.0-dev** by [GLolol](https://github.com/GLolol) on 2016-03-21T19:09:12Z
|
||||
|
||||
### Changes from 0.6.1-dev:
|
||||
|
||||
@ -1532,8 +1420,8 @@ Tagged as **0.7.0-dev** by [jlu5](https://github.com/jlu5) on 2016-03-21T19:09:1
|
||||
- 4b939ea641284aa9bbb796adc58d273f080e59ee ts6: rewrite end-of-burst code (EOB is literally just a PING in ts6)
|
||||
- 5a68dc1bc5f880d1117ca81e729f90fb5e1fce38 Irc: don't call initVars() on IRC object initialization
|
||||
|
||||
# [PyLink 0.6.1-dev](https://github.com/jlu5/PyLink/releases/tag/0.6.1-dev)
|
||||
Tagged as **0.6.1-dev** by [jlu5](https://github.com/jlu5) on 2016-03-02T05:15:22Z
|
||||
# [PyLink 0.6.1-dev](https://github.com/GLolol/PyLink/releases/tag/0.6.1-dev)
|
||||
Tagged as **0.6.1-dev** by [GLolol](https://github.com/GLolol) on 2016-03-02T05:15:22Z
|
||||
|
||||
* Bug fix release.
|
||||
- unreal: fix handing of users connecting via IPv4 3c3ae10
|
||||
@ -1541,8 +1429,8 @@ Tagged as **0.6.1-dev** by [jlu5](https://github.com/jlu5) on 2016-03-02T05:15:2
|
||||
- inspircd, ts6: don't crash when receiving an unrecognized UID 341c208
|
||||
- inspircd: format kill reasons like `Killed (sourcenick (reason))` properly.
|
||||
|
||||
# [PyLink 0.6.0-dev](https://github.com/jlu5/PyLink/releases/tag/0.6.0-dev)
|
||||
Tagged as **0.6.0-dev** by [jlu5](https://github.com/jlu5) on 2016-01-23T18:24:10Z
|
||||
# [PyLink 0.6.0-dev](https://github.com/GLolol/PyLink/releases/tag/0.6.0-dev)
|
||||
Tagged as **0.6.0-dev** by [GLolol](https://github.com/GLolol) on 2016-01-23T18:24:10Z
|
||||
|
||||
Notable changes in this release:
|
||||
|
||||
@ -1562,10 +1450,10 @@ Notable changes in this release:
|
||||
- protocols: allow changing remote users' hosts in updateClient (741fed9).
|
||||
- Speed up and clean up shutdown sequence, fixing hangs due to sockets not shutting down cleanly (#152).
|
||||
- protocols/unreal: Support cloaking with user mode `+x` (#136).
|
||||
- Various bug fixes - see https://github.com/jlu5/PyLink/compare/0.5-dev...0.6.0-dev for a full diff.
|
||||
- Various bug fixes - see https://github.com/GLolol/PyLink/compare/0.5-dev...0.6.0-dev for a full diff.
|
||||
|
||||
# [PyLink 0.5-dev](https://github.com/jlu5/PyLink/releases/tag/0.5-dev)
|
||||
Tagged as **0.5-dev** by [jlu5](https://github.com/jlu5) on 2015-12-06T17:54:02Z
|
||||
# [PyLink 0.5-dev](https://github.com/GLolol/PyLink/releases/tag/0.5-dev)
|
||||
Tagged as **0.5-dev** by [GLolol](https://github.com/GLolol) on 2015-12-06T17:54:02Z
|
||||
|
||||
The "We're getting somewhere..." release.
|
||||
|
||||
@ -1591,27 +1479,27 @@ The "We're getting somewhere..." release.
|
||||
- protocols/unreal: **Add (experimental) support for UnrealIRCd 4.0.x!**
|
||||
- plugins: More complete INFO logging: plugin loading/unloading, unknown commands called, successful operups
|
||||
|
||||
Full diff:https://github.com/jlu5/PyLink/compare/0.4.6-dev...0.5-dev
|
||||
Full diff:https://github.com/GLolol/PyLink/compare/0.4.6-dev...0.5-dev
|
||||
|
||||
# [PyLink 0.4.6-dev](https://github.com/jlu5/PyLink/releases/tag/0.4.6-dev)
|
||||
Tagged as **0.4.6-dev** by [jlu5](https://github.com/jlu5) on 2015-10-01T23:44:20Z
|
||||
# [PyLink 0.4.6-dev](https://github.com/GLolol/PyLink/releases/tag/0.4.6-dev)
|
||||
Tagged as **0.4.6-dev** by [GLolol](https://github.com/GLolol) on 2015-10-01T23:44:20Z
|
||||
|
||||
Bugfix release:
|
||||
|
||||
- f20e6775770b7a118a697c8ae08364d850cdf116 relay: fix PMs across the relay (7d919e6 regression)
|
||||
- 55d9eb240f037a3378a92ab7661b31011398f565 classes.Irc: prettier __repr__
|
||||
|
||||
# [PyLink 0.4.5-dev](https://github.com/jlu5/PyLink/releases/tag/0.4.5-dev)
|
||||
Tagged as **0.4.5-dev** by [jlu5](https://github.com/jlu5) on 2015-09-30T04:14:22Z
|
||||
# [PyLink 0.4.5-dev](https://github.com/GLolol/PyLink/releases/tag/0.4.5-dev)
|
||||
Tagged as **0.4.5-dev** by [GLolol](https://github.com/GLolol) on 2015-09-30T04:14:22Z
|
||||
|
||||
The "fancy stuff!" release.
|
||||
|
||||
New features including in-place config reloading (rehashing) (#89), FANTASY support (#111), and plugin (re/un)loading without a restart.
|
||||
|
||||
Full diff since 0.4.0-dev: https://github.com/jlu5/PyLink/compare/0.4.0-dev...0.4.5-dev
|
||||
Full diff since 0.4.0-dev: https://github.com/GLolol/PyLink/compare/0.4.0-dev...0.4.5-dev
|
||||
|
||||
# [PyLink 0.3.50-dev](https://github.com/jlu5/PyLink/releases/tag/0.3.50-dev)
|
||||
Tagged as **0.3.50-dev** by [jlu5](https://github.com/jlu5) on 2015-09-19T18:28:24Z
|
||||
# [PyLink 0.3.50-dev](https://github.com/GLolol/PyLink/releases/tag/0.3.50-dev)
|
||||
Tagged as **0.3.50-dev** by [GLolol](https://github.com/GLolol) on 2015-09-19T18:28:24Z
|
||||
|
||||
Many updates to core, preparing for an (eventual) 0.4.x release. Commits:
|
||||
|
||||
@ -1658,10 +1546,10 @@ Many updates to core, preparing for an (eventual) 0.4.x release. Commits:
|
||||
- 3d621b0 Move checkAuthenticated() to utils, and give it and isOper() toggles for allowing oper/PyLink logins
|
||||
- 090fa85 Move Irc() from main.py to classes.py
|
||||
|
||||
# [PyLink 0.3.1-dev](https://github.com/jlu5/PyLink/releases/tag/0.3.1-dev)
|
||||
Tagged as **0.3.1-dev** by [jlu5](https://github.com/jlu5) on 2015-09-03T06:56:48Z
|
||||
# [PyLink 0.3.1-dev](https://github.com/GLolol/PyLink/releases/tag/0.3.1-dev)
|
||||
Tagged as **0.3.1-dev** by [GLolol](https://github.com/GLolol) on 2015-09-03T06:56:48Z
|
||||
|
||||
Bugfix release + LINKACL support for relay. [Commits since 0.3.0-dev](https://github.com/jlu5/PyLink/compare/0.3.0-dev...0.3.1-dev):
|
||||
Bugfix release + LINKACL support for relay. [Commits since 0.3.0-dev](https://github.com/GLolol/PyLink/compare/0.3.0-dev...0.3.1-dev):
|
||||
|
||||
- 043fccf4470bfbc8041056f5dbb694be079a45a5 Fix previous commit (Closes #100)
|
||||
- 708d94916477f53ddc79a90c4ff321f636c01348 relay: join remote users before sending ours
|
||||
@ -1673,13 +1561,13 @@ Bugfix release + LINKACL support for relay. [Commits since 0.3.0-dev](https://gi
|
||||
- 3523f8f7663e618829dccfbec6eccfaf0ec87cc5 LINKACL support
|
||||
- 51389b96e26224aab262b7b090032d0b745e9590 relay: LINKACL command (Closes #88)
|
||||
|
||||
# [PyLink 0.2.5-dev](https://github.com/jlu5/PyLink/releases/tag/0.2.5-dev)
|
||||
Tagged as **0.2.5-dev** by [jlu5](https://github.com/jlu5) on 2015-08-16T05:39:34Z
|
||||
# [PyLink 0.2.5-dev](https://github.com/GLolol/PyLink/releases/tag/0.2.5-dev)
|
||||
Tagged as **0.2.5-dev** by [GLolol](https://github.com/GLolol) on 2015-08-16T05:39:34Z
|
||||
|
||||
See the diff for this development build: https://github.com/jlu5/PyLink/compare/0.2.3-dev...0.2.5-dev
|
||||
See the diff for this development build: https://github.com/GLolol/PyLink/compare/0.2.3-dev...0.2.5-dev
|
||||
|
||||
# [PyLink 0.2.3-dev](https://github.com/jlu5/PyLink/releases/tag/0.2.3-dev)
|
||||
Tagged as **0.2.3-dev** by [jlu5](https://github.com/jlu5) on 2015-07-26T06:11:20Z
|
||||
# [PyLink 0.2.3-dev](https://github.com/GLolol/PyLink/releases/tag/0.2.3-dev)
|
||||
Tagged as **0.2.3-dev** by [GLolol](https://github.com/GLolol) on 2015-07-26T06:11:20Z
|
||||
|
||||
The "prevent PyLink from wrecking my server's CPU" release.
|
||||
|
||||
@ -1703,10 +1591,10 @@ Mostly bug fixes here, with a couple of scripts added (`start-cpulimit.sh` and `
|
||||
- ts6: fix `JOIN` handling and `parse_as` key handling in hooks (ddefd38)
|
||||
- relay: only wait for `irc.connected` once per network (4d7d7ce)
|
||||
|
||||
Full diff: https://github.com/jlu5/PyLink/compare/0.2.2-dev...0.2.3-dev
|
||||
Full diff: https://github.com/GLolol/PyLink/compare/0.2.2-dev...0.2.3-dev
|
||||
|
||||
# [PyLink 0.2.2-dev](https://github.com/jlu5/PyLink/releases/tag/0.2.2-dev)
|
||||
Tagged as **0.2.2-dev** by [jlu5](https://github.com/jlu5) on 2015-07-24T18:09:44Z
|
||||
# [PyLink 0.2.2-dev](https://github.com/GLolol/PyLink/releases/tag/0.2.2-dev)
|
||||
Tagged as **0.2.2-dev** by [GLolol](https://github.com/GLolol) on 2015-07-24T18:09:44Z
|
||||
|
||||
The "please don't break again :( " release.
|
||||
|
||||
@ -1717,10 +1605,10 @@ The "please don't break again :( " release.
|
||||
|
||||
...And of course, lots and lots of bug fixes; I won't bother to list them all.
|
||||
|
||||
Full diff: https://github.com/jlu5/PyLink/compare/0.2.0-dev...0.2.2-dev
|
||||
Full diff: https://github.com/GLolol/PyLink/compare/0.2.0-dev...0.2.2-dev
|
||||
|
||||
# [PyLink 0.2.0-dev](https://github.com/jlu5/PyLink/releases/tag/0.2.0-dev)
|
||||
Tagged as **0.2.0-dev** by [jlu5](https://github.com/jlu5) on 2015-07-23T04:44:17Z
|
||||
# [PyLink 0.2.0-dev](https://github.com/GLolol/PyLink/releases/tag/0.2.0-dev)
|
||||
Tagged as **0.2.0-dev** by [GLolol](https://github.com/GLolol) on 2015-07-23T04:44:17Z
|
||||
|
||||
Many changes in this development release, including:
|
||||
|
||||
@ -1736,10 +1624,10 @@ Many changes in this development release, including:
|
||||
|
||||
And of course, many, many bug fixes! (relay should now work properly with more than 2 networks, for example...)
|
||||
|
||||
Full diff: https://github.com/jlu5/PyLink/compare/0.1.6-dev...0.2.0-dev
|
||||
Full diff: https://github.com/GLolol/PyLink/compare/0.1.6-dev...0.2.0-dev
|
||||
|
||||
# [PyLink 0.1.6-dev](https://github.com/jlu5/PyLink/releases/tag/0.1.6-dev)
|
||||
Tagged as **0.1.6-dev** by [jlu5](https://github.com/jlu5) on 2015-07-20T06:09:40Z
|
||||
# [PyLink 0.1.6-dev](https://github.com/GLolol/PyLink/releases/tag/0.1.6-dev)
|
||||
Tagged as **0.1.6-dev** by [GLolol](https://github.com/GLolol) on 2015-07-20T06:09:40Z
|
||||
|
||||
### Bug fixes and improvements from 0.1.5-dev
|
||||
|
||||
@ -1751,10 +1639,10 @@ Tagged as **0.1.6-dev** by [jlu5](https://github.com/jlu5) on 2015-07-20T06:09:4
|
||||
- utils: add `getHostmask()` (1b09a00)
|
||||
- various: Log command usage, `exec` usage, successful logins, and access denied errors in `admin.py`'s commands (57e9bf6)
|
||||
|
||||
Full diff: https://github.com/jlu5/PyLink/compare/0.1.5-dev...0.1.6-dev
|
||||
Full diff: https://github.com/GLolol/PyLink/compare/0.1.5-dev...0.1.6-dev
|
||||
|
||||
# [PyLink 0.1.5-dev](https://github.com/jlu5/PyLink/releases/tag/0.1.5-dev)
|
||||
Tagged as **0.1.5-dev** by [jlu5](https://github.com/jlu5) on 2015-07-18T20:01:39Z
|
||||
# [PyLink 0.1.5-dev](https://github.com/GLolol/PyLink/releases/tag/0.1.5-dev)
|
||||
Tagged as **0.1.5-dev** by [GLolol](https://github.com/GLolol) on 2015-07-18T20:01:39Z
|
||||
|
||||
### New features
|
||||
|
||||
@ -1785,10 +1673,10 @@ Tagged as **0.1.5-dev** by [jlu5](https://github.com/jlu5) on 2015-07-18T20:01:3
|
||||
- commands: remove `debug` command; it's useless now that `exec`, `showchan`, and `showuser` exist (50665ec)
|
||||
- admin: `tell` command has been removed. Rationale: limited usefulness; doesn't wrap long messages properly. (4553eda)
|
||||
|
||||
You can view the full diff here: https://github.com/jlu5/PyLink/compare/0.1.0-dev...0.1.5-dev
|
||||
You can view the full diff here: https://github.com/GLolol/PyLink/compare/0.1.0-dev...0.1.5-dev
|
||||
|
||||
# [PyLink 0.1.0-dev](https://github.com/jlu5/PyLink/releases/tag/0.1.0-dev)
|
||||
Tagged as **0.1.0-dev** by [jlu5](https://github.com/jlu5) on 2015-07-16T06:27:12Z
|
||||
# [PyLink 0.1.0-dev](https://github.com/GLolol/PyLink/releases/tag/0.1.0-dev)
|
||||
Tagged as **0.1.0-dev** by [GLolol](https://github.com/GLolol) on 2015-07-16T06:27:12Z
|
||||
|
||||
PyLink's first pre-alpha development snapshot.
|
||||
|
||||
|
183
classes.py
183
classes.py
@ -22,13 +22,9 @@ import threading
|
||||
import time
|
||||
|
||||
from . import __version__, conf, selectdriver, structures, utils, world
|
||||
from .log import log, PyLinkChannelLogger
|
||||
from .log import *
|
||||
from .utils import ProtocolError # Compatibility with PyLink 1.x
|
||||
|
||||
__all__ = ['ChannelState', 'User', 'UserMapping', 'PyLinkNetworkCore',
|
||||
'PyLinkNetworkCoreWithUtils', 'IRCNetwork', 'Server', 'Channel',
|
||||
'PUIDGenerator', 'ProtocolError']
|
||||
|
||||
QUEUE_FULL = queue.Full
|
||||
|
||||
|
||||
@ -112,9 +108,6 @@ class User(TSObject):
|
||||
# Stores service bot name if applicable
|
||||
self.service = None
|
||||
|
||||
# Whether the user is using SSL/TLS (None = unknown)
|
||||
self.ssl = None
|
||||
|
||||
@property
|
||||
def nick(self):
|
||||
return self._nick
|
||||
@ -234,6 +227,7 @@ class PyLinkNetworkCore(structures.CamelCaseToSnakeCase):
|
||||
self.serverdata = conf.conf['servers'][netname]
|
||||
|
||||
self.protoname = self.__class__.__module__.split('.')[-1] # Remove leading pylinkirc.protocols.
|
||||
self.proto = self.irc = self # Backwards compat
|
||||
|
||||
# Protocol stuff
|
||||
self.casemapping = 'rfc1459'
|
||||
@ -287,7 +281,7 @@ class PyLinkNetworkCore(structures.CamelCaseToSnakeCase):
|
||||
level = chandata.get('loglevel')
|
||||
else:
|
||||
log.warning('(%s) Got invalid channel logging pair %r: %r; are your indentation '
|
||||
'and block commenting consistent?', self.name, channel, chandata)
|
||||
'and block commenting consistent?', self.name, filename, config)
|
||||
|
||||
handler = PyLinkChannelLogger(self, channel, level=level)
|
||||
self.loghandlers.append(handler)
|
||||
@ -907,14 +901,6 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
|
||||
'uplink': uplink, 'nicks': affected_nicks, 'serverdata': serverdata,
|
||||
'channeldata': old_channels, 'affected_servers': affected_servers}
|
||||
|
||||
@staticmethod
|
||||
def _log_debug_modes(*args, **kwargs):
|
||||
"""
|
||||
Log debug info related to mode parsing if enabled.
|
||||
"""
|
||||
if conf.conf['pylink'].get('log_mode_parsers'):
|
||||
log.debug(*args, **kwargs)
|
||||
|
||||
def _parse_modes(self, args, existing, supported_modes, is_channel=False, prefixmodes=None,
|
||||
ignore_missing_args=False):
|
||||
"""
|
||||
@ -952,22 +938,22 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
|
||||
if not prefix:
|
||||
prefix = '+'
|
||||
arg = None
|
||||
self._log_debug_modes('Current mode: %s%s; args left: %s', prefix, mode, args)
|
||||
log.debug('Current mode: %s%s; args left: %s', prefix, mode, args)
|
||||
try:
|
||||
if prefixmodes and mode in self.prefixmodes:
|
||||
# We're setting a prefix mode on someone (e.g. +o user1)
|
||||
self._log_debug_modes('Mode %s: This mode is a prefix mode.', mode)
|
||||
log.debug('Mode %s: This mode is a prefix mode.', mode)
|
||||
arg = args.pop(0)
|
||||
# Convert nicks to UIDs implicitly
|
||||
arg = self._get_UID(arg)
|
||||
if arg not in self.users: # Target doesn't exist, skip it.
|
||||
self._log_debug_modes('(%s) Skipping setting mode "%s %s"; the '
|
||||
'target doesn\'t seem to exist!', self.name,
|
||||
mode, arg)
|
||||
log.debug('(%s) Skipping setting mode "%s %s"; the '
|
||||
'target doesn\'t seem to exist!', self.name,
|
||||
mode, arg)
|
||||
continue
|
||||
elif mode in (supported_modes['*A'] + supported_modes['*B']):
|
||||
# Must have parameter.
|
||||
self._log_debug_modes('Mode %s: This mode must have parameter.', mode)
|
||||
log.debug('Mode %s: This mode must have parameter.', mode)
|
||||
arg = args.pop(0)
|
||||
if prefix == '-':
|
||||
if mode in supported_modes['*B'] and arg == '*':
|
||||
@ -980,24 +966,24 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
|
||||
if oldarg:
|
||||
# Set the arg to the old one on the channel.
|
||||
arg = oldarg
|
||||
self._log_debug_modes("Mode %s: coersing argument of '*' to %r.", mode, arg)
|
||||
log.debug("Mode %s: coersing argument of '*' to %r.", mode, arg)
|
||||
|
||||
self._log_debug_modes('(%s) parse_modes: checking if +%s %s is in old modes list: %s; existing_casemap=%s', self.name, mode, arg, existing, existing_casemap)
|
||||
log.debug('(%s) parse_modes: checking if +%s %s is in old modes list: %s; existing_casemap=%s', self.name, mode, arg, existing, existing_casemap)
|
||||
|
||||
arg = self.to_lower(arg)
|
||||
casefolded_modepair = existing_casemap.get((mode, arg)) # Case fold arguments as needed
|
||||
if casefolded_modepair not in existing:
|
||||
# Ignore attempts to unset parameter modes that don't exist.
|
||||
self._log_debug_modes("(%s) parse_modes: ignoring removal of non-existent list mode +%s %s; casefolded_modepair=%s", self.name, mode, arg, casefolded_modepair)
|
||||
log.debug("(%s) parse_modes: ignoring removal of non-existent list mode +%s %s; casefolded_modepair=%s", self.name, mode, arg, casefolded_modepair)
|
||||
continue
|
||||
arg = casefolded_modepair[1]
|
||||
|
||||
elif prefix == '+' and mode in supported_modes['*C']:
|
||||
# Only has parameter when setting.
|
||||
self._log_debug_modes('Mode %s: Only has parameter when setting.', mode)
|
||||
log.debug('Mode %s: Only has parameter when setting.', mode)
|
||||
arg = args.pop(0)
|
||||
except IndexError:
|
||||
logfunc = self._log_debug_modes if ignore_missing_args else log.warning
|
||||
logfunc = log.debug if ignore_missing_args else log.warning
|
||||
logfunc('(%s) Error while parsing mode %r: mode requires an '
|
||||
'argument but none was found. (modestring: %r)',
|
||||
self.name, mode, modestring)
|
||||
@ -1029,17 +1015,17 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
|
||||
|
||||
is_channel = self.is_channel(target)
|
||||
if not is_channel:
|
||||
self._log_debug_modes('(%s) Using self.umodes for this query: %s', self.name, self.umodes)
|
||||
log.debug('(%s) Using self.umodes for this query: %s', self.name, self.umodes)
|
||||
|
||||
if target not in self.users:
|
||||
self._log_debug_modes('(%s) Possible desync! Mode target %s is not in the users index.', self.name, target)
|
||||
log.debug('(%s) Possible desync! Mode target %s is not in the users index.', self.name, target)
|
||||
return [] # Return an empty mode list
|
||||
|
||||
supported_modes = self.umodes
|
||||
oldmodes = self.users[target].modes
|
||||
prefixmodes = None
|
||||
else:
|
||||
self._log_debug_modes('(%s) Using self.cmodes for this query: %s', self.name, self.cmodes)
|
||||
log.debug('(%s) Using self.cmodes for this query: %s', self.name, self.cmodes)
|
||||
|
||||
supported_modes = self.cmodes
|
||||
oldmodes = self._channels[target].modes
|
||||
@ -1087,19 +1073,19 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
|
||||
if real_mode[0] in self.prefixmodes:
|
||||
# Don't add prefix modes to Channel.modes; they belong in the
|
||||
# prefixmodes mapping handled above.
|
||||
self._log_debug_modes('(%s) Not adding mode %s to Channel.modes because '
|
||||
'it\'s a prefix mode.', self.name, str(mode))
|
||||
log.debug('(%s) Not adding mode %s to Channel.modes because '
|
||||
'it\'s a prefix mode.', self.name, str(mode))
|
||||
continue
|
||||
|
||||
if mode[0][0] != '-': # Adding a mode; assume add if no explicit +/- is given
|
||||
self._log_debug_modes('(%s) Adding mode %r on %s', self.name, real_mode, modelist)
|
||||
log.debug('(%s) Adding mode %r on %s', self.name, real_mode, modelist)
|
||||
existing = mapping.get(real_mode[0])
|
||||
if existing and real_mode[0] not in supported_modes['*A']:
|
||||
# The mode we're setting takes a parameter, but is not a list mode (like +beI).
|
||||
# Therefore, only one version of it can exist at a time, and we must remove
|
||||
# any old modepairs using the same letter. Otherwise, we'll get duplicates when,
|
||||
# for example, someone sets mode "+l 30" on a channel already set "+l 25".
|
||||
self._log_debug_modes('(%s) Old modes for mode %r exist in %s, removing them: %s',
|
||||
log.debug('(%s) Old modes for mode %r exist in %s, removing them: %s',
|
||||
self.name, real_mode, modelist, str(existing))
|
||||
while existing:
|
||||
oldvalue = existing.pop()
|
||||
@ -1108,20 +1094,19 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
|
||||
modelist.add(real_mode)
|
||||
mapping[real_mode[0]].add(real_mode[1])
|
||||
else: # Removing a mode
|
||||
self._log_debug_modes('(%s) Removing mode %r from %s', self.name, real_mode, modelist)
|
||||
log.debug('(%s) Removing mode %r from %s', self.name, real_mode, modelist)
|
||||
|
||||
existing = mapping.get(real_mode[0])
|
||||
arg = real_mode[1]
|
||||
# Mode requires argument for removal (case insensitive)
|
||||
if real_mode[0] in (supported_modes['*A'] + supported_modes['*B']):
|
||||
modelist.discard((real_mode[0], self.to_lower(arg)))
|
||||
# Mode does not require argument for removal - remove all modes entries with the same character
|
||||
else:
|
||||
# When removing a mode: check all existing modes with the same character
|
||||
# If no args were needed on removal, remove all modes with that letter
|
||||
# If an arg was given, remove all modes matching the arg (IRC case insensitive)
|
||||
if existing is not None:
|
||||
while existing:
|
||||
oldvalue = existing.pop()
|
||||
if arg is None or self.to_lower(arg) == self.to_lower(oldvalue):
|
||||
modelist.discard((real_mode[0], oldvalue))
|
||||
self._log_debug_modes('(%s) Final modelist: %s', self.name, modelist)
|
||||
log.debug('(%s) Final modelist: %s', self.name, modelist)
|
||||
return modelist
|
||||
|
||||
def apply_modes(self, target, changedmodes):
|
||||
@ -1209,8 +1194,8 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
|
||||
for modepair in oldmodes}
|
||||
|
||||
newmodes = []
|
||||
self._log_debug_modes('(%s) reverse_modes: old/current mode list for %s is: %s', self.name,
|
||||
target, oldmodes)
|
||||
log.debug('(%s) reverse_modes: old/current mode list for %s is: %s', self.name,
|
||||
target, oldmodes)
|
||||
for char, arg in modes:
|
||||
# Mode types:
|
||||
# A = Mode that adds or removes a nick or address to a list. Always has a parameter.
|
||||
@ -1240,27 +1225,27 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
|
||||
arg = self.to_lower(arg)
|
||||
if char[0] != '-' and (mchar, arg) in oldmodes:
|
||||
# Mode is already set.
|
||||
self._log_debug_modes("(%s) reverse_modes: skipping reversing '%s %s' with %s since we're "
|
||||
"setting a mode that's already set.", self.name, char, arg, mpair)
|
||||
log.debug("(%s) reverse_modes: skipping reversing '%s %s' with %s since we're "
|
||||
"setting a mode that's already set.", self.name, char, arg, mpair)
|
||||
continue
|
||||
elif char[0] == '-' and (mchar, arg) not in oldmodes and mchar in possible_modes['*A']:
|
||||
# We're unsetting a list or prefix mode that was never set - don't set it in response!
|
||||
# TS6 IRCds lacks server-side verification for this and can cause annoying mode floods.
|
||||
self._log_debug_modes("(%s) reverse_modes: skipping reversing '%s %s' with %s since it "
|
||||
"wasn't previously set.", self.name, char, arg, mpair)
|
||||
log.debug("(%s) reverse_modes: skipping reversing '%s %s' with %s since it "
|
||||
"wasn't previously set.", self.name, char, arg, mpair)
|
||||
continue
|
||||
elif char[0] == '-' and mchar not in oldmodes_mapping:
|
||||
# Check the same for regular modes that previously didn't exist
|
||||
self._log_debug_modes("(%s) reverse_modes: skipping reversing '%s %s' with %s since it "
|
||||
"wasn't previously set.", self.name, char, arg, mpair)
|
||||
log.debug("(%s) reverse_modes: skipping reversing '%s %s' with %s since it "
|
||||
"wasn't previously set.", self.name, char, arg, mpair)
|
||||
continue
|
||||
elif mpair in newmodes:
|
||||
# Check the same for regular modes that previously didn't exist
|
||||
self._log_debug_modes("(%s) reverse_modes: skipping duplicate reverse mode %s", self.name, mpair)
|
||||
log.debug("(%s) reverse_modes: skipping duplicate reverse mode %s", self.name, mpair)
|
||||
continue
|
||||
newmodes.append(mpair)
|
||||
|
||||
self._log_debug_modes('(%s) reverse_modes: new modes: %s', self.name, newmodes)
|
||||
log.debug('(%s) reverse_modes: new modes: %s', self.name, newmodes)
|
||||
if origstring:
|
||||
# If the original query is a string, send it back as a string.
|
||||
return self.join_modes(newmodes)
|
||||
@ -1363,23 +1348,23 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
|
||||
if (next_length + total_length) <= limit and ((not max_modes_per_msg) or len(queued_modes) < max_modes_per_msg):
|
||||
# We can fit this mode in the next message; add it.
|
||||
total_length += next_length
|
||||
cls._log_debug_modes('wrap_modes: Adding mode %s to queued modes', str(next_mode))
|
||||
log.debug('wrap_modes: Adding mode %s to queued modes', str(next_mode))
|
||||
queued_modes.append(next_mode)
|
||||
cls._log_debug_modes('wrap_modes: queued modes: %s', queued_modes)
|
||||
log.debug('wrap_modes: queued modes: %s', queued_modes)
|
||||
else:
|
||||
# Otherwise, create a new message by joining the previous queued modes into a message.
|
||||
# Then, create a new message with our current mode.
|
||||
strings.append(cls.join_modes(queued_modes))
|
||||
queued_modes.clear()
|
||||
|
||||
cls._log_debug_modes('wrap_modes: cleared queue (length %s) and now adding %s', limit, str(next_mode))
|
||||
log.debug('wrap_modes: cleared queue (length %s) and now adding %s', limit, str(next_mode))
|
||||
queued_modes.append(next_mode)
|
||||
total_length = next_length
|
||||
else:
|
||||
# Everything fit in one line, so just use that.
|
||||
strings.append(cls.join_modes(queued_modes))
|
||||
|
||||
cls._log_debug_modes('wrap_modes: returning %s for %s', strings, orig_modes)
|
||||
log.debug('wrap_modes: returning %s for %s', strings, orig_modes)
|
||||
return strings
|
||||
|
||||
def get_hostmask(self, user, realhost=False, ip=False):
|
||||
@ -1693,17 +1678,26 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
|
||||
uid, nick)
|
||||
self.call_hooks([self.sid, 'SAVE', {'target': uid}])
|
||||
|
||||
def _expandPUID(self, entityid):
|
||||
def _expandPUID(self, uid):
|
||||
"""
|
||||
Returns the nick or server name for the given UID/SID. This method helps support protocol
|
||||
modules that use PUIDs internally, as they must convert them to talk with the uplink.
|
||||
"""
|
||||
log.debug('(%s) _expandPUID: got uid %s', self.name, uid)
|
||||
# TODO: stop hardcoding @ as separator
|
||||
if isinstance(entityid, str) and '@' in entityid:
|
||||
name = self.get_friendly_name(entityid)
|
||||
log.debug('(%s) _expandPUID: mangling pseudo ID %s to %s', self.name, entityid, name)
|
||||
return name
|
||||
return entityid # Regular UID/SID, no change
|
||||
if uid and isinstance(uid, str) and '@' in uid:
|
||||
if uid in self.users:
|
||||
# UID exists and has a @ in it, meaning it's a PUID (orignick@counter style).
|
||||
# Return this user's nick accordingly.
|
||||
nick = self.users[uid].nick
|
||||
log.debug('(%s) Mangling target PUID %s to nick %s', self.name, uid, nick)
|
||||
return nick
|
||||
elif uid in self.servers:
|
||||
# Ditto for servers
|
||||
sname = self.servers[uid].name
|
||||
log.debug('(%s) Mangling target PSID %s to server name %s', self.name, uid, sname)
|
||||
return sname
|
||||
return uid # Regular UID, no change
|
||||
|
||||
def wrap_message(self, source, target, text):
|
||||
"""
|
||||
@ -1726,6 +1720,7 @@ class IRCNetwork(PyLinkNetworkCoreWithUtils):
|
||||
self._queue = None
|
||||
self._ping_timer = None
|
||||
self._socket = None
|
||||
self._selector_key = None
|
||||
self._buffer = bytearray()
|
||||
self._reconnect_thread = None
|
||||
self._queue_thread = None
|
||||
@ -1787,7 +1782,7 @@ class IRCNetwork(PyLinkNetworkCoreWithUtils):
|
||||
# ssl-should-verify defined
|
||||
context.check_hostname = self.serverdata.get('ssl_validate_hostname',
|
||||
self.has_cap("ssl-should-verify") or
|
||||
utils.get_hostname_type(self.serverdata['ip']) == 0)
|
||||
utils.get_hostname_type(self.serverdata['ip']) is 0)
|
||||
|
||||
return context
|
||||
|
||||
@ -1796,7 +1791,6 @@ class IRCNetwork(PyLinkNetworkCoreWithUtils):
|
||||
Initializes SSL/TLS for this network.
|
||||
"""
|
||||
log.info('(%s) Using TLS/SSL for this connection...', self.name)
|
||||
cafile = self.serverdata.get('ssl_cafile')
|
||||
certfile = self.serverdata.get('ssl_certfile')
|
||||
keyfile = self.serverdata.get('ssl_keyfile')
|
||||
|
||||
@ -1805,11 +1799,10 @@ class IRCNetwork(PyLinkNetworkCoreWithUtils):
|
||||
# Cert and key files are optional, load them if specified.
|
||||
if certfile and keyfile:
|
||||
try:
|
||||
cafile != None and context.load_verify_locations(cafile)
|
||||
context.load_cert_chain(certfile, keyfile)
|
||||
except OSError:
|
||||
log.exception('(%s) Caught OSError trying to initialize the SSL connection; '
|
||||
'are "ssl_certfile", "ssl_keyfile", and "ssl_cafile" set correctly?',
|
||||
'are "ssl_certfile" and "ssl_keyfile" set correctly?',
|
||||
self.name)
|
||||
raise
|
||||
|
||||
@ -1859,34 +1852,30 @@ class IRCNetwork(PyLinkNetworkCoreWithUtils):
|
||||
"""
|
||||
self._pre_connect()
|
||||
|
||||
remote = self.serverdata["ip"]
|
||||
ip = self.serverdata["ip"]
|
||||
port = self.serverdata["port"]
|
||||
try:
|
||||
if 'bindhost' in self.serverdata:
|
||||
# Try detecting the socket type from the bindhost if specified.
|
||||
force_ipv6 = utils.get_hostname_type(self.serverdata['bindhost']) == 2
|
||||
else:
|
||||
force_ipv6 = self.serverdata.get("ipv6") # ternary value (None = use system default)
|
||||
# Set the socket type (IPv6 or IPv4), auto detecting it if not specified.
|
||||
isipv6 = self.serverdata.get("ipv6", utils.get_hostname_type(ip) == 2)
|
||||
|
||||
if force_ipv6 is True:
|
||||
dns_stype = socket.AF_INET6
|
||||
elif force_ipv6 is False:
|
||||
dns_stype = socket.AF_INET
|
||||
else:
|
||||
dns_stype = socket.AF_UNSPEC
|
||||
if (not isipv6) and 'bindhost' in self.serverdata:
|
||||
# Also try detecting the socket type from the bindhost if specified.
|
||||
isipv6 = utils.get_hostname_type(self.serverdata['bindhost']) == 2
|
||||
|
||||
dns_result = socket.getaddrinfo(remote, port, family=dns_stype)[0]
|
||||
ip = dns_result[-1][0]
|
||||
stype = socket.AF_INET6 if isipv6 else socket.AF_INET
|
||||
|
||||
log.debug('(%s) Resolving address %s to %s (force_ipv6=%s)', self.name, remote, ip, force_ipv6)
|
||||
|
||||
# Create the actual socket.
|
||||
self._socket = socket.socket(dns_result[0])
|
||||
# Creat the socket.
|
||||
self._socket = socket.socket(stype)
|
||||
|
||||
# Set the socket bind if applicable.
|
||||
if 'bindhost' in self.serverdata:
|
||||
self._socket.bind((self.serverdata['bindhost'], 0))
|
||||
|
||||
# Resolve hostnames if it's not an IP address already.
|
||||
old_ip = ip
|
||||
ip = socket.getaddrinfo(ip, port, stype)[0][-1][0]
|
||||
log.debug('(%s) Resolving address %s to %s', self.name, old_ip, ip)
|
||||
|
||||
# Enable SSL if set to do so.
|
||||
self.ssl = self.serverdata.get('ssl')
|
||||
if self.ssl:
|
||||
@ -1916,7 +1905,7 @@ class IRCNetwork(PyLinkNetworkCoreWithUtils):
|
||||
# Make sure future reads never block, since select doesn't always guarantee this.
|
||||
self._socket.setblocking(False)
|
||||
|
||||
selectdriver.register(self)
|
||||
self._selector_key = selectdriver.register(self)
|
||||
|
||||
if self.ssl:
|
||||
self._verify_ssl()
|
||||
@ -2086,7 +2075,7 @@ class IRCNetwork(PyLinkNetworkCoreWithUtils):
|
||||
|
||||
def _send(self, data):
|
||||
"""Sends raw text to the uplink server."""
|
||||
if self._aborted.is_set() or self._socket is None:
|
||||
if self._aborted.is_set():
|
||||
log.debug("(%s) Not sending message %r since the connection is dead", self.name, data)
|
||||
return
|
||||
|
||||
@ -2100,23 +2089,11 @@ class IRCNetwork(PyLinkNetworkCoreWithUtils):
|
||||
|
||||
log.debug("(%s) -> %s", self.name, data)
|
||||
|
||||
while True:
|
||||
try:
|
||||
self._socket.send(encoded_data)
|
||||
except (BlockingIOError, ssl.SSLWantReadError, ssl.SSLWantWriteError):
|
||||
# The send attempt failed, wait a little bit.
|
||||
# I would prefer using a blocking socket and MSG_DONTWAIT in recv()'s flags
|
||||
# but SSLSocket doesn't support that...
|
||||
throttle_time = self.serverdata.get('throttle_time', 0)
|
||||
if self._aborted.wait(throttle_time):
|
||||
break
|
||||
continue
|
||||
except:
|
||||
log.exception("(%s) Failed to send message %r; aborting!", self.name, data)
|
||||
self.disconnect()
|
||||
return
|
||||
else:
|
||||
break
|
||||
try:
|
||||
self._socket.send(encoded_data)
|
||||
except:
|
||||
log.exception("(%s) Failed to send message %r; aborting!", self.name, data)
|
||||
self.disconnect()
|
||||
|
||||
def send(self, data, queue=True):
|
||||
"""send() wrapper with optional queueing support."""
|
||||
|
5
conf.py
5
conf.py
@ -17,9 +17,6 @@ from collections import defaultdict
|
||||
|
||||
from . import world
|
||||
|
||||
__all__ = ['ConfigurationError', 'conf', 'confname', 'validate', 'load_conf',
|
||||
'get_database_name']
|
||||
|
||||
|
||||
class ConfigurationError(RuntimeError):
|
||||
"""Error when config conditions aren't met."""
|
||||
@ -145,7 +142,7 @@ def get_database_name(dbname):
|
||||
|
||||
This returns '<dbname>.db' if the running config name is PyLink's default
|
||||
(pylink.yml), and '<dbname>-<config name>.db' for anything else. For example,
|
||||
if this is called from an instance running as 'pylink testing.yml', it
|
||||
if this is called from an instance running as './pylink testing.yml', it
|
||||
would return '<dbname>-testing.db'."""
|
||||
if confname != 'pylink':
|
||||
dbname += '-%s' % confname
|
||||
|
@ -4,14 +4,13 @@ control.py - Implements SHUTDOWN and REHASH functionality.
|
||||
import atexit
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import threading
|
||||
|
||||
from pylinkirc import conf, utils, world # Do not import classes, it'll import loop
|
||||
from pylinkirc.log import _get_console_log_level, _make_file_logger, _stop_file_loggers, log
|
||||
|
||||
from . import login
|
||||
|
||||
__all__ = ['remove_network', 'shutdown', 'rehash']
|
||||
from . import login, permissions
|
||||
|
||||
|
||||
def remove_network(ircobj):
|
||||
|
@ -3,14 +3,13 @@ corecommands.py - Implements core PyLink commands.
|
||||
"""
|
||||
|
||||
import gc
|
||||
import importlib
|
||||
import sys
|
||||
|
||||
from pylinkirc import utils, world
|
||||
from pylinkirc import conf, utils, world
|
||||
from pylinkirc.log import log
|
||||
|
||||
from . import control, permissions
|
||||
|
||||
__all__ = []
|
||||
from . import control, login, permissions
|
||||
|
||||
# Essential, core commands go here so that the "commands" plugin with less-important,
|
||||
# but still generic functions can be reloaded.
|
||||
|
@ -5,8 +5,6 @@ exttargets.py - Implements extended targets like $account:xyz, $oper, etc.
|
||||
from pylinkirc import world
|
||||
from pylinkirc.log import log
|
||||
|
||||
__all__ = []
|
||||
|
||||
|
||||
def bind(func):
|
||||
"""
|
||||
@ -47,7 +45,7 @@ def account(irc, host, uid):
|
||||
slogin = irc.to_lower(str(userobj.services_account))
|
||||
|
||||
# Split the given exttarget host into parts, so we know how many to look for.
|
||||
groups = host.split(':')
|
||||
groups = list(map(irc.to_lower, host.split(':')))
|
||||
log.debug('(%s) exttargets.account: groups to match: %s', irc.name, groups)
|
||||
|
||||
if len(groups) == 1:
|
||||
@ -55,12 +53,12 @@ def account(irc, host, uid):
|
||||
return bool(slogin)
|
||||
elif len(groups) == 2:
|
||||
# Second scenario. Return True if the user's account matches the one given.
|
||||
return slogin == irc.to_lower(groups[1]) and homenet == irc.name
|
||||
return slogin == groups[1] and homenet == irc.name
|
||||
else:
|
||||
# Third or fourth scenario. If there are more than 3 groups, the rest are ignored.
|
||||
# In other words: Return True if the user is logged in, the query matches either '*' or the
|
||||
# user's login, and the user is connected on the network requested.
|
||||
return slogin and (irc.to_lower(groups[1]) in ('*', slogin)) and (homenet == groups[2])
|
||||
return slogin and (groups[1] in ('*', slogin)) and (homenet == groups[2])
|
||||
|
||||
@bind
|
||||
def ircop(irc, host, uid):
|
||||
@ -79,8 +77,8 @@ def ircop(irc, host, uid):
|
||||
# 1st scenario.
|
||||
return irc.is_oper(uid)
|
||||
else:
|
||||
# 2nd scenario. Match the opertype glob to the opertype.
|
||||
return irc.match_text(groups[1], irc.users[uid].opertype)
|
||||
# 2nd scenario. Use match_host (ircmatch) to match the opertype glob to the opertype.
|
||||
return irc.match_host(groups[1], irc.users[uid].opertype)
|
||||
|
||||
@bind
|
||||
def server(irc, host, uid):
|
||||
@ -99,7 +97,7 @@ def server(irc, host, uid):
|
||||
sid = irc.get_server(uid)
|
||||
query = groups[1]
|
||||
# Return True if the SID matches the query or the server's name glob matches it.
|
||||
return sid == query or irc.match_text(query, irc.get_friendly_name(sid))
|
||||
return sid == query or irc.match_host(query, irc.get_friendly_name(sid))
|
||||
# $server alone is invalid. Don't match anything.
|
||||
return False
|
||||
|
||||
@ -208,7 +206,7 @@ def realname(irc, host, uid):
|
||||
"""
|
||||
groups = host.split(':')
|
||||
if len(groups) >= 2:
|
||||
return irc.match_text(groups[1], irc.users[uid].realname)
|
||||
return irc.match_host(groups[1], irc.users[uid].realname)
|
||||
|
||||
@bind
|
||||
def service(irc, host, uid):
|
||||
@ -225,5 +223,5 @@ def service(irc, host, uid):
|
||||
groups = host.split(':')
|
||||
|
||||
if len(groups) >= 2:
|
||||
return irc.match_text(groups[1], irc.users[uid].service)
|
||||
return irc.match_host(groups[1], irc.users[uid].service)
|
||||
return True # It *is* a service bot because of the check at the top.
|
||||
|
@ -6,8 +6,6 @@ import time
|
||||
from pylinkirc import conf, utils
|
||||
from pylinkirc.log import log
|
||||
|
||||
__all__ = []
|
||||
|
||||
|
||||
def handle_whois(irc, source, command, args):
|
||||
"""Handle WHOIS queries."""
|
||||
@ -20,7 +18,7 @@ def handle_whois(irc, source, command, args):
|
||||
server = irc.get_server(target)
|
||||
|
||||
if user is None: # User doesn't exist
|
||||
# <- :42X 401 7PYAAAAAB jlu5- :No such nick/channel
|
||||
# <- :42X 401 7PYAAAAAB GL- :No such nick/channel
|
||||
nick = target
|
||||
f(401, source, "%s :No such nick/channel" % nick)
|
||||
else:
|
||||
@ -86,7 +84,7 @@ def handle_whois(irc, source, command, args):
|
||||
n = 'n' if opertype[0].lower() in 'aeiou' else ''
|
||||
|
||||
# Remove the "(on $network)" bit in relay oper types if the target network is the
|
||||
# same - this prevents duplicate text such as "jlu5/ovd is a Network Administrator
|
||||
# same - this prevents duplicate text such as "GL/ovd is a Network Administrator
|
||||
# (on OVERdrive-IRC) on OVERdrive-IRC" from showing.
|
||||
# XXX: does this post-processing really belong here?
|
||||
opertype = opertype.replace(' (on %s)' % irc.get_full_network_name(), '')
|
||||
@ -109,7 +107,7 @@ def handle_whois(irc, source, command, args):
|
||||
# Show botmode info in WHOIS.
|
||||
f(335, source, "%s :is a bot" % nick)
|
||||
|
||||
# :charybdis.midnight.vpn 317 jlu5 jlu5 1946 1499867833 :seconds idle, signon time
|
||||
# :charybdis.midnight.vpn 317 GL GL 1946 1499867833 :seconds idle, signon time
|
||||
if irc.get_service_bot(target) and conf.conf['pylink'].get('whois_show_startup_time', True):
|
||||
f(317, source, "%s 0 %s :seconds idle (placeholder), signon time" % (nick, irc.start_ts))
|
||||
|
||||
|
@ -2,12 +2,9 @@
|
||||
login.py - Implement core login abstraction.
|
||||
"""
|
||||
|
||||
from pylinkirc import conf, utils
|
||||
from pylinkirc import conf, utils, world
|
||||
from pylinkirc.log import log
|
||||
|
||||
__all__ = ['pwd_context', 'check_login', 'verify_hash']
|
||||
|
||||
|
||||
# PyLink's global password context
|
||||
pwd_context = None
|
||||
|
||||
|
@ -2,14 +2,12 @@
|
||||
permissions.py - Permissions Abstraction for PyLink IRC Services.
|
||||
"""
|
||||
|
||||
import threading
|
||||
from collections import defaultdict
|
||||
|
||||
from pylinkirc import conf, utils
|
||||
from pylinkirc.log import log
|
||||
|
||||
__all__ = ['default_permissions', 'add_default_permissions',
|
||||
'remove_default_permissions', 'check_permissions']
|
||||
|
||||
# Global variables: these store mappings of hostmasks/exttargets to lists of permissions each target has.
|
||||
default_permissions = defaultdict(set)
|
||||
|
||||
|
@ -5,8 +5,6 @@ service_support.py - Implements handlers for the PyLink ServiceBot API.
|
||||
from pylinkirc import conf, utils, world
|
||||
from pylinkirc.log import log
|
||||
|
||||
__all__ = []
|
||||
|
||||
|
||||
def spawn_service(irc, source, command, args):
|
||||
"""Handles new service bot introductions."""
|
||||
|
@ -1,5 +1,5 @@
|
||||
This folder contains tables of named modes defined by PyLink modules. The following are HTML versions of the raw .csv data:
|
||||
|
||||
- [Supported named channel modes](https://raw.githack.com/jlu5/PyLink/devel/docs/modelists/channel-modes.html)
|
||||
- [Supported named user modes](https://raw.githack.com/jlu5/PyLink/devel/docs/modelists/user-modes.html)
|
||||
- [Supported extbans](https://raw.githack.com/jlu5/PyLink/devel/docs/modelists/extbans.html)
|
||||
- [Supported named channel modes](https://raw.githack.com/GLolol/PyLink/devel/docs/modelists/channel-modes.html)
|
||||
- [Supported named user modes](https://raw.githack.com/GLolol/PyLink/devel/docs/modelists/user-modes.html)
|
||||
- [Supported extbans](https://raw.githack.com/GLolol/PyLink/devel/docs/modelists/extbans.html)
|
||||
|
@ -1,68 +1,68 @@
|
||||
Channel Mode / IRCd,rfc1459,hybrid,inspircd/insp20,inspircd/insp3,ngircd,p10/ircu,p10/nefarious,p10/snircd,ts6/charybdis,ts6/chatircd,ts6/elemental,ts6/ratbox,unreal
|
||||
admin,,,"a (m_customprefix, m_chanprotect)",a (customprefix),a,,,,,a (when enabled),a (when enabled),,a
|
||||
admin,,,"a (m_customprefix, m_chanprotect)",a (m_customprefix),a,,,,,a (when enabled),a (when enabled),,a
|
||||
adminonly,,,,,,,a,,A (ext/chm_adminonly),A (ext/chm_adminonly),A (ext/chm_adminonly.so),,
|
||||
allowinvite,,,A (m_allowinvite),A (allowinvite),,,,,g,g,g,,
|
||||
auditorium,,,u (m_auditorium),u (auditorium),,,,,,,,,
|
||||
autoop,,,w (m_autoop),w (autoop),,,,,,,,,
|
||||
allowinvite,,,A (m_allowinvite),A (m_allowinvite),,,,,g,g,g,,
|
||||
auditorium,,,u (m_auditorium),u (m_auditorium),,,,,,,,,
|
||||
autoop,,,w (m_autoop),w (m_autoop),,,,,,,,,
|
||||
ban,b,b,b,b,b,b,b,b,b,b,b,b,b
|
||||
banexception,,e,e (m_banexception),e (banexception),e,,e,,e,e,e,e,e
|
||||
blockcaps,,,B (m_blockcaps),"B (anticaps, blockcaps)",,,,,,,G (ext/chm_nocaps.so),,
|
||||
blockcolor,,c,c (m_blockcolor),c (blockcolor),,c,c,c,,,,,c (chanmodes/nocolor)
|
||||
blockhighlight,,,V (contrib/m_blockhighlight),V (contrib/blockhighlight),,,,,,,,,
|
||||
censor,,,G (m_censor),G (censor),,,,,,,,,G (chanmodes/censor)
|
||||
delayjoin,,,D (m_delayjoin),D (delayjoin),,D,D,D,,,,,D (chanmodes/delayjoin)
|
||||
delaymsg,,,d (m_delaymsg),d (delaymsg),,,,,,,,,
|
||||
exemptchanops,,,X (m_exemptchanops),X (exemptchanops),,,,,,,,,
|
||||
filter,,,g (m_filter),g (filter),,,,,,,,,(via extban ~T:block:)
|
||||
flood,,,f (m_messageflood),f (messageflood),,,,,,,,,
|
||||
banexception,,e,e (m_banexception),e (m_banexception),e,,e,,e,e,e,e,e
|
||||
blockcaps,,,B (m_blockcaps),"B (m_anticaps, m_blockcaps)",,,,,,,G (ext/chm_nocaps.so),,
|
||||
blockcolor,,c,c (m_blockcolor),c (m_blockcolor),,c,c,c,,,,,c (chanmodes/nocolor)
|
||||
blockhighlight,,,V (contrib/m_blockhighlight),V (contrib/m_blockhighlight),,,,,,,,,
|
||||
censor,,,G (m_censor),G (m_censor),,,,,,,,,G (chanmodes/censor)
|
||||
delayjoin,,,D (m_delayjoin),D (m_delayjoin),,D,D,D,,,,,D (chanmodes/delayjoin)
|
||||
delaymsg,,,d (m_delaymsg),d (m_delaymsg),,,,,,,,,
|
||||
exemptchanops,,,X (m_exemptchanops),X (m_exemptchanops),,,,,,,,,
|
||||
filter,,,g (m_filter),g (m_filter),,,,,,,,,(via extban ~T:block:)
|
||||
flood,,,f (m_messageflood),f (m_messageflood),,,,,,,,,
|
||||
flood_unreal,,,,,,,,,,,,,f (chanmodes/floodprot)
|
||||
freetarget,,,,,,,,,F,F,F,,
|
||||
had_delayjoin,,,,,,d,d,d,,,,,
|
||||
halfop,,h,"h (m_customprefix, m_halfop)",h (customprefix),h,,,,,h (when enabled),h (when enabled),,h
|
||||
halfop,,h,"h (m_customprefix, m_halfop)",h (m_customprefix),h,,,,,h (when enabled),h (when enabled),,h
|
||||
hiddenbans,,,,,,,,,,,u,,
|
||||
hidequits,,,,,,,Q,u,,,,,
|
||||
history,,,H (m_chanhistory),H (chanhistory),,,,,,,,,
|
||||
invex,,I,I (m_inviteexception),I (inviteexception),I,,,,I,I,I,I,I
|
||||
history,,,H (m_chanhistory),H (m_chanhistory),,,,,,,,,
|
||||
invex,,I,I (m_inviteexception),I (m_inviteexception),I,,,,I,I,I,I,I
|
||||
inviteonly,i,i,i,i,i,i,i,i,i,i,i,i,i
|
||||
issecure,,,,,,,,,,,,,Z (chanmodes/issecure)
|
||||
joinflood,,,j (m_joinflood),j (joinflood),,,,,j,j,j,,
|
||||
joinflood,,,j (m_joinflood),j (m_joinflood),,,,,j,j,j,,
|
||||
key,k,k,k,k,k,k,k,k,k,k,k,k,k
|
||||
kicknorejoin,,,,,,,,,,,J,,
|
||||
kicknorejoin_insp,,,J (m_kicknorejoin),J (kicknorejoin),,,,,,,,,
|
||||
kicknorejoin_insp,,,J (m_kicknorejoin),J (m_kicknorejoin),,,,,,,,,
|
||||
largebanlist,,,,,,,,,L,L,L,,
|
||||
limit,l,l,l,l,l,l,l,l,l,l,l,l,l
|
||||
moderated,m,m,m,m,m,m,m,m,m,m,m,m,m
|
||||
netadminonly,,,,,,,,,,N (ext/chm_netadminonly),,,
|
||||
nickflood,,,F (m_nickflood),F (nickflood),,,,,,,,,
|
||||
nickflood,,,F (m_nickflood),F (m_nickflood),,,,,,,,,
|
||||
noamsg,,,,,,,T,T,,,,,
|
||||
noctcp,,C,C (m_noctcp),C (noctcp),,C,C,C,C,C,C,,C (chanmodes/noctcp)
|
||||
noextmsg,n,n,n,n,n,n,n,n,n,n,n,n,n
|
||||
noctcp,,C,C (m_noctcp),C (m_noctcp),,C,C,C,C,C,C,,C (chanmodes/noctcp)
|
||||
noextmsg,n,n,n,n,n,,n,,n,n,n,n,n
|
||||
noforwards,,,,,,,,,Q,Q,Q,,
|
||||
noinvite,,,,,V,,,,,,,,V (chanmodes/noinvite)
|
||||
nokick,,,Q (m_nokicks),Q (nokicks),Q,,,,,,E,,Q (chanmodes/nokick)
|
||||
noknock,,p*,K (m_knock),K (knock),,,,,p*,p*,p*,p*,K (chanmodes/noknock)
|
||||
nonick,,,N (m_nonicks),N (nonicks),N,,,,,,d,,N (chanmodes/nonickchange)
|
||||
nonotice,,,T (m_nonotice),T (nonotice),,,N,N,T (ext/chm_nonotice),T (ext/chm_nonotice),T,,T (chanmodes/nonotice)
|
||||
official-join,,,Y (m_ojoin),Y (ojoin),,,,,,,,,
|
||||
nokick,,,Q (m_nokicks),Q (m_nokicks),Q,,,,,,E,,Q (chanmodes/nokick)
|
||||
noknock,,p*,K (m_knock),K (m_knock),,,,,p*,p*,p*,p*,K (chanmodes/noknock)
|
||||
nonick,,,N (m_nonicks),N (m_nonicks),N,,,,,,d,,N (chanmodes/nonickchange)
|
||||
nonotice,,,T (m_nonotice),T (m_nonotice),,,N,N,T (ext/chm_nonotice),T (ext/chm_nonotice),T,,T (chanmodes/nonotice)
|
||||
official-join,,,Y (m_ojoin),Y (m_ojoin),,,,,,,,,
|
||||
op,o,o,o,o,o,o,o,o,o,o,o,o,o
|
||||
operonly,,O,O (m_operchans),O (operchans),O,,O,,O (ext/chm_operonly),O (ext/chm_operonly),O (ext/chm_operonly.so),,O (chanmodes/operonly)
|
||||
operonly,,O,O (m_operchans),O (m_operchans),O,,O,,O (ext/chm_operonly),O (ext/chm_operonly),O (ext/chm_operonly.so),,O (chanmodes/operonly)
|
||||
oplevel_apass,,,,,,A,A,A,,,,,
|
||||
oplevel_upass,,,,,,U,U,U,,,,,
|
||||
opmoderated,,,U (contrib/m_opmoderated),,,,,,z,z,z,,
|
||||
owner,,,"q (m_customprefix, m_chanprotect)",q (customprefix),q,,,,,y (when enabled),y (when enabled),,q
|
||||
owner,,,"q (m_customprefix, m_chanprotect)",q (m_customprefix),q,,,,,y (when enabled),y (when enabled),,q
|
||||
paranoia,,p*,,,,,,,,,,,
|
||||
permanent,,,P (m_permchannels),P (permchannels),P,,z,,P,P,P,,P (chanmodes/permanent)
|
||||
permanent,,,P (m_permchannels),P (m_permchannels),P,,z,,P,P,P,,P (chanmodes/permanent)
|
||||
private,p,p*,p,p,p,p,p,p,p*,p*,p*,p*,p
|
||||
quiet,,,(via extban m:),(via extban m:),,,(via extban ~q:),,q,q,q,,(via extban ~q:)
|
||||
redirect,,,L (m_redirect),L (redirect),,,L,,f,f,f,,L (chanmodes/link)
|
||||
registered,,r,r (m_services_account),r (services_account),r,R,R,R,,,,,r
|
||||
regmoderated,,M,M (m_services_account),M (services_account),M,,M,M,,,,,M (chanmodes/regonlyspeak)
|
||||
regonly,,R,R (m_services_account),R (services_account),R,r,r,r,r,r,r,r,R (chanmodes/regonly)
|
||||
redirect,,,L (m_redirect),L (m_redirect),,,L,,f,f,f,,L (chanmodes/link)
|
||||
registered,,r,r (m_services_account),r (m_services_account),r,R,R,R,,,,,r
|
||||
regmoderated,,M,M (m_services_account),M (m_services_account),M,,M,M,,,,,M (chanmodes/regonlyspeak)
|
||||
regonly,,R,R (m_services_account),R (m_services_account),R,r,r,r,r,r,r,r,R (chanmodes/regonly)
|
||||
repeat,,,,,,,,,,,K (ext/chm_norepeat.c),,
|
||||
repeat_insp,,,,E (repeat),,,,,,,,,
|
||||
repeat_insp,,,,E (m_repeat),,,,,,,,,
|
||||
secret,s,s,s,s,s,s,s,s,s,s,s,s,s
|
||||
sslonly,,S,z (m_sslmodes),z (sslmodes),z,,,,S (ext/chm_sslonly),S (ext/chm_sslonly),S (ext/chm_sslonly.c),S,z (chanmodes/secureonly)
|
||||
stripcolor,,,S (m_stripcolor),S (stripcolor),,,S,,c,c,c,,S (chanmodes/stripcolor)
|
||||
sslonly,,S,z (m_sslmodes),z (m_sslmodes),z,,,,S (ext/chm_sslonly),S (ext/chm_sslonly),S (ext/chm_sslonly.c),S,z (chanmodes/secureonly)
|
||||
stripcolor,,,S (m_stripcolor),S (m_stripcolor),,,S,,c,c,c,,S (chanmodes/stripcolor)
|
||||
topiclock,t,t,t,t,t,t,t,t,t,t,t,t,t
|
||||
voice,v,v,v,v,v,v,v,v,v,v,v,v,v
|
||||
,,,,,,,,,,,,,
|
||||
|
|
@ -78,52 +78,52 @@ td:first-child, th[scope="row"] {
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">admin</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+a<br><span class="note">(m_customprefix, m_chanprotect)</span></td><td class="tablecell-yes2">+a<br><span class="note">(customprefix)</span></td><td class="tablecell-yes">+a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+a<br><span class="note">(when enabled)</span></td><td class="tablecell-yes2">+a<br><span class="note">(when enabled)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+a</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+a<br><span class="note">(m_customprefix, m_chanprotect)</span></td><td class="tablecell-yes2">+a<br><span class="note">(m_customprefix)</span></td><td class="tablecell-yes">+a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+a<br><span class="note">(when enabled)</span></td><td class="tablecell-yes2">+a<br><span class="note">(when enabled)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">adminonly</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+A<br><span class="note">(ext/chm_adminonly)</span></td><td class="tablecell-yes2">+A<br><span class="note">(ext/chm_adminonly)</span></td><td class="tablecell-yes2">+A<br><span class="note">(ext/chm_adminonly.so)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">allowinvite</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+A<br><span class="note">(m_allowinvite)</span></td><td class="tablecell-yes2">+A<br><span class="note">(allowinvite)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+g</td><td class="tablecell-yes">+g</td><td class="tablecell-yes">+g</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+A<br><span class="note">(m_allowinvite)</span></td><td class="tablecell-yes2">+A<br><span class="note">(m_allowinvite)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+g</td><td class="tablecell-yes">+g</td><td class="tablecell-yes">+g</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">auditorium</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+u<br><span class="note">(m_auditorium)</span></td><td class="tablecell-yes2">+u<br><span class="note">(auditorium)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+u<br><span class="note">(m_auditorium)</span></td><td class="tablecell-yes2">+u<br><span class="note">(m_auditorium)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">autoop</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+w<br><span class="note">(m_autoop)</span></td><td class="tablecell-yes2">+w<br><span class="note">(autoop)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+w<br><span class="note">(m_autoop)</span></td><td class="tablecell-yes2">+w<br><span class="note">(m_autoop)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">ban</th>
|
||||
<td class="tablecell-yes">+b</td><td class="tablecell-yes">+b</td><td class="tablecell-yes">+b</td><td class="tablecell-yes">+b</td><td class="tablecell-yes">+b</td><td class="tablecell-yes">+b</td><td class="tablecell-yes">+b</td><td class="tablecell-yes">+b</td><td class="tablecell-yes">+b</td><td class="tablecell-yes">+b</td><td class="tablecell-yes">+b</td><td class="tablecell-yes">+b</td><td class="tablecell-yes">+b</td></tr>
|
||||
<tr>
|
||||
<th scope="row">banexception</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+e</td><td class="tablecell-yes2">+e<br><span class="note">(m_banexception)</span></td><td class="tablecell-yes2">+e<br><span class="note">(banexception)</span></td><td class="tablecell-yes">+e</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+e</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+e</td><td class="tablecell-yes">+e</td><td class="tablecell-yes">+e</td><td class="tablecell-yes">+e</td><td class="tablecell-yes">+e</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+e</td><td class="tablecell-yes2">+e<br><span class="note">(m_banexception)</span></td><td class="tablecell-yes2">+e<br><span class="note">(m_banexception)</span></td><td class="tablecell-yes">+e</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+e</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+e</td><td class="tablecell-yes">+e</td><td class="tablecell-yes">+e</td><td class="tablecell-yes">+e</td><td class="tablecell-yes">+e</td></tr>
|
||||
<tr>
|
||||
<th scope="row">blockcaps</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+B<br><span class="note">(m_blockcaps)</span></td><td class="tablecell-yes2">+B<br><span class="note">(anticaps, blockcaps)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+G<br><span class="note">(ext/chm_nocaps.so)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+B<br><span class="note">(m_blockcaps)</span></td><td class="tablecell-yes2">+B<br><span class="note">(m_anticaps, m_blockcaps)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+G<br><span class="note">(ext/chm_nocaps.so)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">blockcolor</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+c</td><td class="tablecell-yes2">+c<br><span class="note">(m_blockcolor)</span></td><td class="tablecell-yes2">+c<br><span class="note">(blockcolor)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+c</td><td class="tablecell-yes">+c</td><td class="tablecell-yes">+c</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+c<br><span class="note">(chanmodes/nocolor)</span></td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+c</td><td class="tablecell-yes2">+c<br><span class="note">(m_blockcolor)</span></td><td class="tablecell-yes2">+c<br><span class="note">(m_blockcolor)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+c</td><td class="tablecell-yes">+c</td><td class="tablecell-yes">+c</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+c<br><span class="note">(chanmodes/nocolor)</span></td></tr>
|
||||
<tr>
|
||||
<th scope="row">blockhighlight</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+V<br><span class="note">(contrib/m_blockhighlight)</span></td><td class="tablecell-yes2">+V<br><span class="note">(contrib/blockhighlight)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+V<br><span class="note">(contrib/m_blockhighlight)</span></td><td class="tablecell-yes2">+V<br><span class="note">(contrib/m_blockhighlight)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">censor</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+G<br><span class="note">(m_censor)</span></td><td class="tablecell-yes2">+G<br><span class="note">(censor)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+G<br><span class="note">(chanmodes/censor)</span></td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+G<br><span class="note">(m_censor)</span></td><td class="tablecell-yes2">+G<br><span class="note">(m_censor)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+G<br><span class="note">(chanmodes/censor)</span></td></tr>
|
||||
<tr>
|
||||
<th scope="row">delayjoin</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+D<br><span class="note">(m_delayjoin)</span></td><td class="tablecell-yes2">+D<br><span class="note">(delayjoin)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+D</td><td class="tablecell-yes">+D</td><td class="tablecell-yes">+D</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+D<br><span class="note">(chanmodes/delayjoin)</span></td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+D<br><span class="note">(m_delayjoin)</span></td><td class="tablecell-yes2">+D<br><span class="note">(m_delayjoin)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+D</td><td class="tablecell-yes">+D</td><td class="tablecell-yes">+D</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+D<br><span class="note">(chanmodes/delayjoin)</span></td></tr>
|
||||
<tr>
|
||||
<th scope="row">delaymsg</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+d<br><span class="note">(m_delaymsg)</span></td><td class="tablecell-yes2">+d<br><span class="note">(delaymsg)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+d<br><span class="note">(m_delaymsg)</span></td><td class="tablecell-yes2">+d<br><span class="note">(m_delaymsg)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">exemptchanops</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+X<br><span class="note">(m_exemptchanops)</span></td><td class="tablecell-yes2">+X<br><span class="note">(exemptchanops)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+X<br><span class="note">(m_exemptchanops)</span></td><td class="tablecell-yes2">+X<br><span class="note">(m_exemptchanops)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">filter</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+g<br><span class="note">(m_filter)</span></td><td class="tablecell-yes2">+g<br><span class="note">(filter)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-partial">(via extban ~T:block:)</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+g<br><span class="note">(m_filter)</span></td><td class="tablecell-yes2">+g<br><span class="note">(m_filter)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-partial">(via extban ~T:block:)</td></tr>
|
||||
<tr>
|
||||
<th scope="row">flood</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+f<br><span class="note">(m_messageflood)</span></td><td class="tablecell-yes2">+f<br><span class="note">(messageflood)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+f<br><span class="note">(m_messageflood)</span></td><td class="tablecell-yes2">+f<br><span class="note">(m_messageflood)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">flood_unreal</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+f<br><span class="note">(chanmodes/floodprot)</span></td></tr>
|
||||
@ -135,7 +135,7 @@ td:first-child, th[scope="row"] {
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+d</td><td class="tablecell-yes">+d</td><td class="tablecell-yes">+d</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">halfop</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+h</td><td class="tablecell-yes2">+h<br><span class="note">(m_customprefix, m_halfop)</span></td><td class="tablecell-yes2">+h<br><span class="note">(customprefix)</span></td><td class="tablecell-yes">+h</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+h<br><span class="note">(when enabled)</span></td><td class="tablecell-yes2">+h<br><span class="note">(when enabled)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+h</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+h</td><td class="tablecell-yes2">+h<br><span class="note">(m_customprefix, m_halfop)</span></td><td class="tablecell-yes2">+h<br><span class="note">(m_customprefix)</span></td><td class="tablecell-yes">+h</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+h<br><span class="note">(when enabled)</span></td><td class="tablecell-yes2">+h<br><span class="note">(when enabled)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+h</td></tr>
|
||||
<tr>
|
||||
<th scope="row">hiddenbans</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+u</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
@ -144,10 +144,10 @@ td:first-child, th[scope="row"] {
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+Q</td><td class="tablecell-yes">+u</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">history</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+H<br><span class="note">(m_chanhistory)</span></td><td class="tablecell-yes2">+H<br><span class="note">(chanhistory)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+H<br><span class="note">(m_chanhistory)</span></td><td class="tablecell-yes2">+H<br><span class="note">(m_chanhistory)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">invex</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+I</td><td class="tablecell-yes2">+I<br><span class="note">(m_inviteexception)</span></td><td class="tablecell-yes2">+I<br><span class="note">(inviteexception)</span></td><td class="tablecell-yes">+I</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+I</td><td class="tablecell-yes">+I</td><td class="tablecell-yes">+I</td><td class="tablecell-yes">+I</td><td class="tablecell-yes">+I</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+I</td><td class="tablecell-yes2">+I<br><span class="note">(m_inviteexception)</span></td><td class="tablecell-yes2">+I<br><span class="note">(m_inviteexception)</span></td><td class="tablecell-yes">+I</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+I</td><td class="tablecell-yes">+I</td><td class="tablecell-yes">+I</td><td class="tablecell-yes">+I</td><td class="tablecell-yes">+I</td></tr>
|
||||
<tr>
|
||||
<th scope="row">inviteonly</th>
|
||||
<td class="tablecell-yes">+i</td><td class="tablecell-yes">+i</td><td class="tablecell-yes">+i</td><td class="tablecell-yes">+i</td><td class="tablecell-yes">+i</td><td class="tablecell-yes">+i</td><td class="tablecell-yes">+i</td><td class="tablecell-yes">+i</td><td class="tablecell-yes">+i</td><td class="tablecell-yes">+i</td><td class="tablecell-yes">+i</td><td class="tablecell-yes">+i</td><td class="tablecell-yes">+i</td></tr>
|
||||
@ -156,7 +156,7 @@ td:first-child, th[scope="row"] {
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+Z<br><span class="note">(chanmodes/issecure)</span></td></tr>
|
||||
<tr>
|
||||
<th scope="row">joinflood</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+j<br><span class="note">(m_joinflood)</span></td><td class="tablecell-yes2">+j<br><span class="note">(joinflood)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+j</td><td class="tablecell-yes">+j</td><td class="tablecell-yes">+j</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+j<br><span class="note">(m_joinflood)</span></td><td class="tablecell-yes2">+j<br><span class="note">(m_joinflood)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+j</td><td class="tablecell-yes">+j</td><td class="tablecell-yes">+j</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">key</th>
|
||||
<td class="tablecell-yes">+k</td><td class="tablecell-yes">+k</td><td class="tablecell-yes">+k</td><td class="tablecell-yes">+k</td><td class="tablecell-yes">+k</td><td class="tablecell-yes">+k</td><td class="tablecell-yes">+k</td><td class="tablecell-yes">+k</td><td class="tablecell-yes">+k</td><td class="tablecell-yes">+k</td><td class="tablecell-yes">+k</td><td class="tablecell-yes">+k</td><td class="tablecell-yes">+k</td></tr>
|
||||
@ -165,7 +165,7 @@ td:first-child, th[scope="row"] {
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+J</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">kicknorejoin_insp</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+J<br><span class="note">(m_kicknorejoin)</span></td><td class="tablecell-yes2">+J<br><span class="note">(kicknorejoin)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+J<br><span class="note">(m_kicknorejoin)</span></td><td class="tablecell-yes2">+J<br><span class="note">(m_kicknorejoin)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">largebanlist</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+L</td><td class="tablecell-yes">+L</td><td class="tablecell-yes">+L</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
@ -180,16 +180,16 @@ td:first-child, th[scope="row"] {
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+N<br><span class="note">(ext/chm_netadminonly)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">nickflood</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+F<br><span class="note">(m_nickflood)</span></td><td class="tablecell-yes2">+F<br><span class="note">(nickflood)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+F<br><span class="note">(m_nickflood)</span></td><td class="tablecell-yes2">+F<br><span class="note">(m_nickflood)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">noamsg</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+T</td><td class="tablecell-yes">+T</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">noctcp</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+C</td><td class="tablecell-yes2">+C<br><span class="note">(m_noctcp)</span></td><td class="tablecell-yes2">+C<br><span class="note">(noctcp)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+C</td><td class="tablecell-yes">+C</td><td class="tablecell-yes">+C</td><td class="tablecell-yes">+C</td><td class="tablecell-yes">+C</td><td class="tablecell-yes">+C</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+C<br><span class="note">(chanmodes/noctcp)</span></td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+C</td><td class="tablecell-yes2">+C<br><span class="note">(m_noctcp)</span></td><td class="tablecell-yes2">+C<br><span class="note">(m_noctcp)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+C</td><td class="tablecell-yes">+C</td><td class="tablecell-yes">+C</td><td class="tablecell-yes">+C</td><td class="tablecell-yes">+C</td><td class="tablecell-yes">+C</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+C<br><span class="note">(chanmodes/noctcp)</span></td></tr>
|
||||
<tr>
|
||||
<th scope="row">noextmsg</th>
|
||||
<td class="tablecell-yes">+n</td><td class="tablecell-yes">+n</td><td class="tablecell-yes">+n</td><td class="tablecell-yes">+n</td><td class="tablecell-yes">+n</td><td class="tablecell-yes">+n</td><td class="tablecell-yes">+n</td><td class="tablecell-yes">+n</td><td class="tablecell-yes">+n</td><td class="tablecell-yes">+n</td><td class="tablecell-yes">+n</td><td class="tablecell-yes">+n</td><td class="tablecell-yes">+n</td></tr>
|
||||
<td class="tablecell-yes">+n</td><td class="tablecell-yes">+n</td><td class="tablecell-yes">+n</td><td class="tablecell-yes">+n</td><td class="tablecell-yes">+n</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+n</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+n</td><td class="tablecell-yes">+n</td><td class="tablecell-yes">+n</td><td class="tablecell-yes">+n</td><td class="tablecell-yes">+n</td></tr>
|
||||
<tr>
|
||||
<th scope="row">noforwards</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+Q</td><td class="tablecell-yes">+Q</td><td class="tablecell-yes">+Q</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
@ -198,25 +198,25 @@ td:first-child, th[scope="row"] {
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+V</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+V<br><span class="note">(chanmodes/noinvite)</span></td></tr>
|
||||
<tr>
|
||||
<th scope="row">nokick</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+Q<br><span class="note">(m_nokicks)</span></td><td class="tablecell-yes2">+Q<br><span class="note">(nokicks)</span></td><td class="tablecell-yes">+Q</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+E</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+Q<br><span class="note">(chanmodes/nokick)</span></td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+Q<br><span class="note">(m_nokicks)</span></td><td class="tablecell-yes2">+Q<br><span class="note">(m_nokicks)</span></td><td class="tablecell-yes">+Q</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+E</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+Q<br><span class="note">(chanmodes/nokick)</span></td></tr>
|
||||
<tr>
|
||||
<th scope="row">noknock</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-special">+p*</td><td class="tablecell-yes2">+K<br><span class="note">(m_knock)</span></td><td class="tablecell-yes2">+K<br><span class="note">(knock)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-special">+p*</td><td class="tablecell-special">+p*</td><td class="tablecell-special">+p*</td><td class="tablecell-special">+p*</td><td class="tablecell-yes2">+K<br><span class="note">(chanmodes/noknock)</span></td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-special">+p*</td><td class="tablecell-yes2">+K<br><span class="note">(m_knock)</span></td><td class="tablecell-yes2">+K<br><span class="note">(m_knock)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-special">+p*</td><td class="tablecell-special">+p*</td><td class="tablecell-special">+p*</td><td class="tablecell-special">+p*</td><td class="tablecell-yes2">+K<br><span class="note">(chanmodes/noknock)</span></td></tr>
|
||||
<tr>
|
||||
<th scope="row">nonick</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+N<br><span class="note">(m_nonicks)</span></td><td class="tablecell-yes2">+N<br><span class="note">(nonicks)</span></td><td class="tablecell-yes">+N</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+d</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+N<br><span class="note">(chanmodes/nonickchange)</span></td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+N<br><span class="note">(m_nonicks)</span></td><td class="tablecell-yes2">+N<br><span class="note">(m_nonicks)</span></td><td class="tablecell-yes">+N</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+d</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+N<br><span class="note">(chanmodes/nonickchange)</span></td></tr>
|
||||
<tr>
|
||||
<th scope="row">nonotice</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+T<br><span class="note">(m_nonotice)</span></td><td class="tablecell-yes2">+T<br><span class="note">(nonotice)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+N</td><td class="tablecell-yes">+N</td><td class="tablecell-yes2">+T<br><span class="note">(ext/chm_nonotice)</span></td><td class="tablecell-yes2">+T<br><span class="note">(ext/chm_nonotice)</span></td><td class="tablecell-yes">+T</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+T<br><span class="note">(chanmodes/nonotice)</span></td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+T<br><span class="note">(m_nonotice)</span></td><td class="tablecell-yes2">+T<br><span class="note">(m_nonotice)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+N</td><td class="tablecell-yes">+N</td><td class="tablecell-yes2">+T<br><span class="note">(ext/chm_nonotice)</span></td><td class="tablecell-yes2">+T<br><span class="note">(ext/chm_nonotice)</span></td><td class="tablecell-yes">+T</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+T<br><span class="note">(chanmodes/nonotice)</span></td></tr>
|
||||
<tr>
|
||||
<th scope="row">official-join</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+Y<br><span class="note">(m_ojoin)</span></td><td class="tablecell-yes2">+Y<br><span class="note">(ojoin)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+Y<br><span class="note">(m_ojoin)</span></td><td class="tablecell-yes2">+Y<br><span class="note">(m_ojoin)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">op</th>
|
||||
<td class="tablecell-yes">+o</td><td class="tablecell-yes">+o</td><td class="tablecell-yes">+o</td><td class="tablecell-yes">+o</td><td class="tablecell-yes">+o</td><td class="tablecell-yes">+o</td><td class="tablecell-yes">+o</td><td class="tablecell-yes">+o</td><td class="tablecell-yes">+o</td><td class="tablecell-yes">+o</td><td class="tablecell-yes">+o</td><td class="tablecell-yes">+o</td><td class="tablecell-yes">+o</td></tr>
|
||||
<tr>
|
||||
<th scope="row">operonly</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+O</td><td class="tablecell-yes2">+O<br><span class="note">(m_operchans)</span></td><td class="tablecell-yes2">+O<br><span class="note">(operchans)</span></td><td class="tablecell-yes">+O</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+O</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+O<br><span class="note">(ext/chm_operonly)</span></td><td class="tablecell-yes2">+O<br><span class="note">(ext/chm_operonly)</span></td><td class="tablecell-yes2">+O<br><span class="note">(ext/chm_operonly.so)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+O<br><span class="note">(chanmodes/operonly)</span></td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+O</td><td class="tablecell-yes2">+O<br><span class="note">(m_operchans)</span></td><td class="tablecell-yes2">+O<br><span class="note">(m_operchans)</span></td><td class="tablecell-yes">+O</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+O</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+O<br><span class="note">(ext/chm_operonly)</span></td><td class="tablecell-yes2">+O<br><span class="note">(ext/chm_operonly)</span></td><td class="tablecell-yes2">+O<br><span class="note">(ext/chm_operonly.so)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+O<br><span class="note">(chanmodes/operonly)</span></td></tr>
|
||||
<tr>
|
||||
<th scope="row">oplevel_apass</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+A</td><td class="tablecell-yes">+A</td><td class="tablecell-yes">+A</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
@ -228,13 +228,13 @@ td:first-child, th[scope="row"] {
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+U<br><span class="note">(contrib/m_opmoderated)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+z</td><td class="tablecell-yes">+z</td><td class="tablecell-yes">+z</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">owner</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+q<br><span class="note">(m_customprefix, m_chanprotect)</span></td><td class="tablecell-yes2">+q<br><span class="note">(customprefix)</span></td><td class="tablecell-yes">+q</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+y<br><span class="note">(when enabled)</span></td><td class="tablecell-yes2">+y<br><span class="note">(when enabled)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+q</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+q<br><span class="note">(m_customprefix, m_chanprotect)</span></td><td class="tablecell-yes2">+q<br><span class="note">(m_customprefix)</span></td><td class="tablecell-yes">+q</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+y<br><span class="note">(when enabled)</span></td><td class="tablecell-yes2">+y<br><span class="note">(when enabled)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+q</td></tr>
|
||||
<tr>
|
||||
<th scope="row">paranoia</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-special">+p*</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">permanent</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+P<br><span class="note">(m_permchannels)</span></td><td class="tablecell-yes2">+P<br><span class="note">(permchannels)</span></td><td class="tablecell-yes">+P</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+z</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+P</td><td class="tablecell-yes">+P</td><td class="tablecell-yes">+P</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+P<br><span class="note">(chanmodes/permanent)</span></td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+P<br><span class="note">(m_permchannels)</span></td><td class="tablecell-yes2">+P<br><span class="note">(m_permchannels)</span></td><td class="tablecell-yes">+P</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+z</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+P</td><td class="tablecell-yes">+P</td><td class="tablecell-yes">+P</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+P<br><span class="note">(chanmodes/permanent)</span></td></tr>
|
||||
<tr>
|
||||
<th scope="row">private</th>
|
||||
<td class="tablecell-yes">+p</td><td class="tablecell-special">+p*</td><td class="tablecell-yes">+p</td><td class="tablecell-yes">+p</td><td class="tablecell-yes">+p</td><td class="tablecell-yes">+p</td><td class="tablecell-yes">+p</td><td class="tablecell-yes">+p</td><td class="tablecell-special">+p*</td><td class="tablecell-special">+p*</td><td class="tablecell-special">+p*</td><td class="tablecell-special">+p*</td><td class="tablecell-yes">+p</td></tr>
|
||||
@ -243,31 +243,31 @@ td:first-child, th[scope="row"] {
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-partial">(via extban m:)</td><td class="tablecell-partial">(via extban m:)</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-partial">(via extban ~q:)</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+q</td><td class="tablecell-yes">+q</td><td class="tablecell-yes">+q</td><td class="tablecell-na note">n/a</td><td class="tablecell-partial">(via extban ~q:)</td></tr>
|
||||
<tr>
|
||||
<th scope="row">redirect</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+L<br><span class="note">(m_redirect)</span></td><td class="tablecell-yes2">+L<br><span class="note">(redirect)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+L</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+f</td><td class="tablecell-yes">+f</td><td class="tablecell-yes">+f</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+L<br><span class="note">(chanmodes/link)</span></td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+L<br><span class="note">(m_redirect)</span></td><td class="tablecell-yes2">+L<br><span class="note">(m_redirect)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+L</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+f</td><td class="tablecell-yes">+f</td><td class="tablecell-yes">+f</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+L<br><span class="note">(chanmodes/link)</span></td></tr>
|
||||
<tr>
|
||||
<th scope="row">registered</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+r</td><td class="tablecell-yes2">+r<br><span class="note">(m_services_account)</span></td><td class="tablecell-yes2">+r<br><span class="note">(services_account)</span></td><td class="tablecell-yes">+r</td><td class="tablecell-yes">+R</td><td class="tablecell-yes">+R</td><td class="tablecell-yes">+R</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+r</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+r</td><td class="tablecell-yes2">+r<br><span class="note">(m_services_account)</span></td><td class="tablecell-yes2">+r<br><span class="note">(m_services_account)</span></td><td class="tablecell-yes">+r</td><td class="tablecell-yes">+R</td><td class="tablecell-yes">+R</td><td class="tablecell-yes">+R</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+r</td></tr>
|
||||
<tr>
|
||||
<th scope="row">regmoderated</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+M</td><td class="tablecell-yes2">+M<br><span class="note">(m_services_account)</span></td><td class="tablecell-yes2">+M<br><span class="note">(services_account)</span></td><td class="tablecell-yes">+M</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+M</td><td class="tablecell-yes">+M</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+M<br><span class="note">(chanmodes/regonlyspeak)</span></td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+M</td><td class="tablecell-yes2">+M<br><span class="note">(m_services_account)</span></td><td class="tablecell-yes2">+M<br><span class="note">(m_services_account)</span></td><td class="tablecell-yes">+M</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+M</td><td class="tablecell-yes">+M</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+M<br><span class="note">(chanmodes/regonlyspeak)</span></td></tr>
|
||||
<tr>
|
||||
<th scope="row">regonly</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+R</td><td class="tablecell-yes2">+R<br><span class="note">(m_services_account)</span></td><td class="tablecell-yes2">+R<br><span class="note">(services_account)</span></td><td class="tablecell-yes">+R</td><td class="tablecell-yes">+r</td><td class="tablecell-yes">+r</td><td class="tablecell-yes">+r</td><td class="tablecell-yes">+r</td><td class="tablecell-yes">+r</td><td class="tablecell-yes">+r</td><td class="tablecell-yes">+r</td><td class="tablecell-yes2">+R<br><span class="note">(chanmodes/regonly)</span></td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+R</td><td class="tablecell-yes2">+R<br><span class="note">(m_services_account)</span></td><td class="tablecell-yes2">+R<br><span class="note">(m_services_account)</span></td><td class="tablecell-yes">+R</td><td class="tablecell-yes">+r</td><td class="tablecell-yes">+r</td><td class="tablecell-yes">+r</td><td class="tablecell-yes">+r</td><td class="tablecell-yes">+r</td><td class="tablecell-yes">+r</td><td class="tablecell-yes">+r</td><td class="tablecell-yes2">+R<br><span class="note">(chanmodes/regonly)</span></td></tr>
|
||||
<tr>
|
||||
<th scope="row">repeat</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+K<br><span class="note">(ext/chm_norepeat.c)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">repeat_insp</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+E<br><span class="note">(repeat)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+E<br><span class="note">(m_repeat)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">secret</th>
|
||||
<td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td></tr>
|
||||
<tr>
|
||||
<th scope="row">sslonly</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+S</td><td class="tablecell-yes2">+z<br><span class="note">(m_sslmodes)</span></td><td class="tablecell-yes2">+z<br><span class="note">(sslmodes)</span></td><td class="tablecell-yes">+z</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+S<br><span class="note">(ext/chm_sslonly)</span></td><td class="tablecell-yes2">+S<br><span class="note">(ext/chm_sslonly)</span></td><td class="tablecell-yes2">+S<br><span class="note">(ext/chm_sslonly.c)</span></td><td class="tablecell-yes">+S</td><td class="tablecell-yes2">+z<br><span class="note">(chanmodes/secureonly)</span></td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+S</td><td class="tablecell-yes2">+z<br><span class="note">(m_sslmodes)</span></td><td class="tablecell-yes2">+z<br><span class="note">(m_sslmodes)</span></td><td class="tablecell-yes">+z</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+S<br><span class="note">(ext/chm_sslonly)</span></td><td class="tablecell-yes2">+S<br><span class="note">(ext/chm_sslonly)</span></td><td class="tablecell-yes2">+S<br><span class="note">(ext/chm_sslonly.c)</span></td><td class="tablecell-yes">+S</td><td class="tablecell-yes2">+z<br><span class="note">(chanmodes/secureonly)</span></td></tr>
|
||||
<tr>
|
||||
<th scope="row">stripcolor</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+S<br><span class="note">(m_stripcolor)</span></td><td class="tablecell-yes2">+S<br><span class="note">(stripcolor)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+S</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+c</td><td class="tablecell-yes">+c</td><td class="tablecell-yes">+c</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+S<br><span class="note">(chanmodes/stripcolor)</span></td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+S<br><span class="note">(m_stripcolor)</span></td><td class="tablecell-yes2">+S<br><span class="note">(m_stripcolor)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+S</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+c</td><td class="tablecell-yes">+c</td><td class="tablecell-yes">+c</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+S<br><span class="note">(chanmodes/stripcolor)</span></td></tr>
|
||||
<tr>
|
||||
<th scope="row">topiclock</th>
|
||||
<td class="tablecell-yes">+t</td><td class="tablecell-yes">+t</td><td class="tablecell-yes">+t</td><td class="tablecell-yes">+t</td><td class="tablecell-yes">+t</td><td class="tablecell-yes">+t</td><td class="tablecell-yes">+t</td><td class="tablecell-yes">+t</td><td class="tablecell-yes">+t</td><td class="tablecell-yes">+t</td><td class="tablecell-yes">+t</td><td class="tablecell-yes">+t</td><td class="tablecell-yes">+t</td></tr>
|
||||
|
@ -1,39 +1,39 @@
|
||||
User Mode / IRCd,RFC 1459,hybrid,inspircd,ngircd,p10/ircu,p10/nefarious,p10/snircd,ts6/charybdis,ts6/chatircd,ts6/elemental,ts6/ratbox,unreal
|
||||
admin,,a,,,,a,,a,a,a,a,
|
||||
away,,,,a,,,,,,,,
|
||||
bot,,,B (botmode),B,,B,,,B,B,,B (usermodes/bot)
|
||||
callerid,,g,g (callerid),,,,,g,g,g,g,
|
||||
censor,,,,,,,,,,,,G (usermodes/censor)
|
||||
cloak,,x,x (cloaking),x,x,x,x,x,x,x,,x
|
||||
bot,,,B,B,,B,,,B,B,,B
|
||||
callerid,,g,g,,,,,g,g,g,g,
|
||||
callerid_sslonly,,,,,,,,,t,,,
|
||||
cloak,,x,x,x,x,x,x,x,x,x,,x
|
||||
cloak_fakehost,,,,,,f,,,,,,
|
||||
cloak_hashedhost,,,,,,C,,,,,,
|
||||
cloak_hashedip,,,,,,c,,,,,,
|
||||
cloak_sethost,,,,,,h,h,,,,,
|
||||
deaf,,D,d (deaf),,d,d,d,D,D,D,D,d
|
||||
deaf_commonchan,,G,c (commonchans),C,,q,,,,,,
|
||||
deaf,,D,d,b,d,d,d,D,D,D,D,d
|
||||
deaf_commonchan,,G,c,C,,q,,,,,,
|
||||
debug,,d,,,,,,,,,,
|
||||
filter,,,,,,,,,,,,G
|
||||
floodexempt,,,,F,,,,,,,,
|
||||
helpop,,,h (helpop),,,,,,,,,
|
||||
hidechans,,p,I (hidechans),I,,n,n,,,I,,p (usermodes/privacy)
|
||||
floodexempt,,,,f,,,,,,,,
|
||||
helpop,,,h,,,,,,,,,
|
||||
hidechans,,p,I,I,,n,n,,,I,,p
|
||||
hideidle,,q,,,,I,I,,,,,I
|
||||
hideoper,,H,H (hideoper),,,H,,,,,,H
|
||||
hideoper,,H,H,,,H,,,,,,H
|
||||
invisible,i,i,i,i,i,i,i,i,i,i,i,i
|
||||
locops,,l,,,O,O,O,l,l,l,l,
|
||||
netadmin,,,,,,,,,N,,,
|
||||
noctcp,,,,,,,,,,C,,T (usermodes/noctcp)
|
||||
noforward,,,L (redirect),,,L,,Q,Q,Q,,
|
||||
noctcp,,,,,,,,,,C,,T
|
||||
noforward,,,,,,L,,Q,Q,Q,,
|
||||
noinvite,,,,,,,,,,V,,
|
||||
oper,o,o,o,o,o,o,o,o,o,o,o,o
|
||||
operwall,,,,,,,,z,z,z,z,
|
||||
override,,,,,,X,X,p,p,p,,
|
||||
privdeaf,,,,b,,D,,,,,,D (usermodes/privdeaf)
|
||||
protected,,,,,,,,,,,,q (usermodes/nokick)
|
||||
regdeaf,,R,R (services_account),,,R,R,R,R,R,,R (usermodes/regonlymsg)
|
||||
registered,,r,r (services_account),R,r,r,r,,,,,r
|
||||
privdeaf,,,,,,D,,,,,,
|
||||
protected,,,,,,,,,,,,q
|
||||
regdeaf,,R,R,,,R,R,R,R,R,,R
|
||||
registered,,r,r,R,r,r,r,,,,,r
|
||||
restricted,,,,r,,,,,,,,
|
||||
servprotect,,,k (servprotect),q,k,k,k,S,S,S,S,S (usermodes/servicebot)
|
||||
showwhois,,,W (showwhois),,,W,,,,,,W (usermodes/showwhois)
|
||||
servprotect,,,k,q,k,k,k,S,S,S,S,S
|
||||
showwhois,,,W,,,W,,,,,,W
|
||||
sno_badclientconnections,,u,,,,,,,,,u,
|
||||
sno_botfloods,,b,,,,,,,,,b,
|
||||
sno_clientconnections,,c,,c,,,,,,,c,
|
||||
@ -46,10 +46,9 @@ sno_remoteclientconnections,,F,,,,,,,,,,
|
||||
sno_serverconnects,,e,,,,,,,,,x,
|
||||
sno_skill,,k,,,,,,,,,k,
|
||||
sno_stats,,y,,,,,,,,,y,
|
||||
snomask,s,s,s,s,s,s,s,s,s,s,s,s
|
||||
snomask,s,s,s,s,,s,,s,s,s,s,s
|
||||
ssl,,S,,,,z,,,,,,z
|
||||
sslonlymsg,,,,,,,,,t,,,Z (usermodes/secureonlymsg)
|
||||
stripcolor,,,S (stripcolor),,,,,,,,,
|
||||
stripcolor,,,S,,,,,,,,,
|
||||
vhost,,,,,,,,,,,,t
|
||||
wallops,w,w,w,w,w,w,w,w,w,w,w,w
|
||||
wallops,w,w,w,w,,w,,w,w,w,w,w
|
||||
webirc,,W,,,,,,,,,,
|
||||
|
|
@ -83,16 +83,16 @@ td:first-child, th[scope="row"] {
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">bot</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+B<br><span class="note">(botmode)</span></td><td class="tablecell-yes">+B</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+B</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+B</td><td class="tablecell-yes">+B</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+B<br><span class="note">(usermodes/bot)</span></td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+B</td><td class="tablecell-yes">+B</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+B</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+B</td><td class="tablecell-yes">+B</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+B</td></tr>
|
||||
<tr>
|
||||
<th scope="row">callerid</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+g</td><td class="tablecell-yes2">+g<br><span class="note">(callerid)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+g</td><td class="tablecell-yes">+g</td><td class="tablecell-yes">+g</td><td class="tablecell-yes">+g</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+g</td><td class="tablecell-yes">+g</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+g</td><td class="tablecell-yes">+g</td><td class="tablecell-yes">+g</td><td class="tablecell-yes">+g</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">censor</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+G<br><span class="note">(usermodes/censor)</span></td></tr>
|
||||
<th scope="row">callerid_sslonly</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+t</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">cloak</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+x</td><td class="tablecell-yes2">+x<br><span class="note">(cloaking)</span></td><td class="tablecell-yes">+x</td><td class="tablecell-yes">+x</td><td class="tablecell-yes">+x</td><td class="tablecell-yes">+x</td><td class="tablecell-yes">+x</td><td class="tablecell-yes">+x</td><td class="tablecell-yes">+x</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+x</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+x</td><td class="tablecell-yes">+x</td><td class="tablecell-yes">+x</td><td class="tablecell-yes">+x</td><td class="tablecell-yes">+x</td><td class="tablecell-yes">+x</td><td class="tablecell-yes">+x</td><td class="tablecell-yes">+x</td><td class="tablecell-yes">+x</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+x</td></tr>
|
||||
<tr>
|
||||
<th scope="row">cloak_fakehost</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+f</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
@ -107,10 +107,10 @@ td:first-child, th[scope="row"] {
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+h</td><td class="tablecell-yes">+h</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">deaf</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+D</td><td class="tablecell-yes2">+d<br><span class="note">(deaf)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+d</td><td class="tablecell-yes">+d</td><td class="tablecell-yes">+d</td><td class="tablecell-yes">+D</td><td class="tablecell-yes">+D</td><td class="tablecell-yes">+D</td><td class="tablecell-yes">+D</td><td class="tablecell-yes">+d</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+D</td><td class="tablecell-yes">+d</td><td class="tablecell-yes">+b</td><td class="tablecell-yes">+d</td><td class="tablecell-yes">+d</td><td class="tablecell-yes">+d</td><td class="tablecell-yes">+D</td><td class="tablecell-yes">+D</td><td class="tablecell-yes">+D</td><td class="tablecell-yes">+D</td><td class="tablecell-yes">+d</td></tr>
|
||||
<tr>
|
||||
<th scope="row">deaf_commonchan</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+G</td><td class="tablecell-yes2">+c<br><span class="note">(commonchans)</span></td><td class="tablecell-yes">+C</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+q</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+G</td><td class="tablecell-yes">+c</td><td class="tablecell-yes">+C</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+q</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">debug</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+d</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
@ -119,19 +119,19 @@ td:first-child, th[scope="row"] {
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+G</td></tr>
|
||||
<tr>
|
||||
<th scope="row">floodexempt</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+F</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+f</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">helpop</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+h<br><span class="note">(helpop)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+h</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">hidechans</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+p</td><td class="tablecell-yes2">+I<br><span class="note">(hidechans)</span></td><td class="tablecell-yes">+I</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+n</td><td class="tablecell-yes">+n</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+I</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+p<br><span class="note">(usermodes/privacy)</span></td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+p</td><td class="tablecell-yes">+I</td><td class="tablecell-yes">+I</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+n</td><td class="tablecell-yes">+n</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+I</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+p</td></tr>
|
||||
<tr>
|
||||
<th scope="row">hideidle</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+q</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+I</td><td class="tablecell-yes">+I</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+I</td></tr>
|
||||
<tr>
|
||||
<th scope="row">hideoper</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+H</td><td class="tablecell-yes2">+H<br><span class="note">(hideoper)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+H</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+H</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+H</td><td class="tablecell-yes">+H</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+H</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+H</td></tr>
|
||||
<tr>
|
||||
<th scope="row">invisible</th>
|
||||
<td class="tablecell-yes">+i</td><td class="tablecell-yes">+i</td><td class="tablecell-yes">+i</td><td class="tablecell-yes">+i</td><td class="tablecell-yes">+i</td><td class="tablecell-yes">+i</td><td class="tablecell-yes">+i</td><td class="tablecell-yes">+i</td><td class="tablecell-yes">+i</td><td class="tablecell-yes">+i</td><td class="tablecell-yes">+i</td><td class="tablecell-yes">+i</td></tr>
|
||||
@ -143,10 +143,10 @@ td:first-child, th[scope="row"] {
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+N</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">noctcp</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+C</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+T<br><span class="note">(usermodes/noctcp)</span></td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+C</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+T</td></tr>
|
||||
<tr>
|
||||
<th scope="row">noforward</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+L<br><span class="note">(redirect)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+L</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+Q</td><td class="tablecell-yes">+Q</td><td class="tablecell-yes">+Q</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+L</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+Q</td><td class="tablecell-yes">+Q</td><td class="tablecell-yes">+Q</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">noinvite</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+V</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
@ -161,25 +161,25 @@ td:first-child, th[scope="row"] {
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+X</td><td class="tablecell-yes">+X</td><td class="tablecell-yes">+p</td><td class="tablecell-yes">+p</td><td class="tablecell-yes">+p</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">privdeaf</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+b</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+D</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+D<br><span class="note">(usermodes/privdeaf)</span></td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+D</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">protected</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+q<br><span class="note">(usermodes/nokick)</span></td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+q</td></tr>
|
||||
<tr>
|
||||
<th scope="row">regdeaf</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+R</td><td class="tablecell-yes2">+R<br><span class="note">(services_account)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+R</td><td class="tablecell-yes">+R</td><td class="tablecell-yes">+R</td><td class="tablecell-yes">+R</td><td class="tablecell-yes">+R</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+R<br><span class="note">(usermodes/regonlymsg)</span></td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+R</td><td class="tablecell-yes">+R</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+R</td><td class="tablecell-yes">+R</td><td class="tablecell-yes">+R</td><td class="tablecell-yes">+R</td><td class="tablecell-yes">+R</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+R</td></tr>
|
||||
<tr>
|
||||
<th scope="row">registered</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+r</td><td class="tablecell-yes2">+r<br><span class="note">(services_account)</span></td><td class="tablecell-yes">+R</td><td class="tablecell-yes">+r</td><td class="tablecell-yes">+r</td><td class="tablecell-yes">+r</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+r</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+r</td><td class="tablecell-yes">+r</td><td class="tablecell-yes">+R</td><td class="tablecell-yes">+r</td><td class="tablecell-yes">+r</td><td class="tablecell-yes">+r</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+r</td></tr>
|
||||
<tr>
|
||||
<th scope="row">restricted</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+r</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">servprotect</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+k<br><span class="note">(servprotect)</span></td><td class="tablecell-yes">+q</td><td class="tablecell-yes">+k</td><td class="tablecell-yes">+k</td><td class="tablecell-yes">+k</td><td class="tablecell-yes">+S</td><td class="tablecell-yes">+S</td><td class="tablecell-yes">+S</td><td class="tablecell-yes">+S</td><td class="tablecell-yes2">+S<br><span class="note">(usermodes/servicebot)</span></td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+k</td><td class="tablecell-yes">+q</td><td class="tablecell-yes">+k</td><td class="tablecell-yes">+k</td><td class="tablecell-yes">+k</td><td class="tablecell-yes">+S</td><td class="tablecell-yes">+S</td><td class="tablecell-yes">+S</td><td class="tablecell-yes">+S</td><td class="tablecell-yes">+S</td></tr>
|
||||
<tr>
|
||||
<th scope="row">showwhois</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+W<br><span class="note">(showwhois)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+W</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+W<br><span class="note">(usermodes/showwhois)</span></td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+W</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+W</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+W</td></tr>
|
||||
<tr>
|
||||
<th scope="row">sno_badclientconnections</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+u</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+u</td><td class="tablecell-na note">n/a</td></tr>
|
||||
@ -218,22 +218,19 @@ td:first-child, th[scope="row"] {
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+y</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+y</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">snomask</th>
|
||||
<td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td></tr>
|
||||
<td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+s</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td><td class="tablecell-yes">+s</td></tr>
|
||||
<tr>
|
||||
<th scope="row">ssl</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+S</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+z</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+z</td></tr>
|
||||
<tr>
|
||||
<th scope="row">sslonlymsg</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+t</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+Z<br><span class="note">(usermodes/secureonlymsg)</span></td></tr>
|
||||
<tr>
|
||||
<th scope="row">stripcolor</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes2">+S<br><span class="note">(stripcolor)</span></td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+S</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
<tr>
|
||||
<th scope="row">vhost</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+t</td></tr>
|
||||
<tr>
|
||||
<th scope="row">wallops</th>
|
||||
<td class="tablecell-yes">+w</td><td class="tablecell-yes">+w</td><td class="tablecell-yes">+w</td><td class="tablecell-yes">+w</td><td class="tablecell-yes">+w</td><td class="tablecell-yes">+w</td><td class="tablecell-yes">+w</td><td class="tablecell-yes">+w</td><td class="tablecell-yes">+w</td><td class="tablecell-yes">+w</td><td class="tablecell-yes">+w</td><td class="tablecell-yes">+w</td></tr>
|
||||
<td class="tablecell-yes">+w</td><td class="tablecell-yes">+w</td><td class="tablecell-yes">+w</td><td class="tablecell-yes">+w</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+w</td><td class="tablecell-na note">n/a</td><td class="tablecell-yes">+w</td><td class="tablecell-yes">+w</td><td class="tablecell-yes">+w</td><td class="tablecell-yes">+w</td><td class="tablecell-yes">+w</td></tr>
|
||||
<tr>
|
||||
<th scope="row">webirc</th>
|
||||
<td class="tablecell-na note">n/a</td><td class="tablecell-yes">+W</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td><td class="tablecell-na note">n/a</td></tr>
|
||||
|
@ -1,22 +1,17 @@
|
||||
# PyLink Relay Quick Start
|
||||
# PyLink Relay Quick Start Guide
|
||||
|
||||
## What is Relay?
|
||||
PyLink Relay (aka "Relay") provides transparent server-side relaying between channels, letting networks share channels on demand without going through all the fuss of a hard link. Each network retains its own opers and services, with default behaviour being so that oper features (kill, overrides, etc.) are isolated to only work on channels they own. If you're familiar with Janus, you can think of PyLink Relay as being a rewrite of it from scratch (though PyLink can do much more via its other plugins!).
|
||||
|
||||
PyLink Relay is a plugin that provides transparent relays between channels on different networks. On participating networks, PyLink connects as a services server and mirrors messages as well as user lists from relayed channels, the latter by creating "puppet" service clients for all remote users in common channels. Relay offers an alternative to classic IRC linking, letting networks share channels on demand while retaining their services, policies, and distinct branding. By default, Relay also secures channels from remote oper overrides via a CLAIM feature, which restricts /kick, /mode, and /topic changes from un-opped users unless they are granted permissions via CLAIM.
|
||||
This guide goes over some of the basic commands in Relay, as well as all the must-know notes.
|
||||
|
||||
Relay shares many ideas from its predecessor Janus, but is a complete rewrite in Python. This guide goes over some of the basic commands in Relay, as well as some must-know gotchas.
|
||||
## How nick suffixing work
|
||||
|
||||
## Important notes (READ FIRST!)
|
||||
The default Relay configuration in will automatically tag users from other networks with a suffix such as `/net`. The purpose of this is to prevent confusing nick collisions if the same nick is used on multiple linked networks, and ensure that remote networks' nicks effectively use their own namespace.
|
||||
|
||||
### How nick suffixing work
|
||||
How is this relevant to an operator? Firstly, it means that you **cannot ban users** using banmasks such as `*/net1!*@*`! The nick suffix is something PyLink adds artificially; on `net1`'s IRCd, which check the bans locally, the nick suffix doesn't exist and will therefore *not* match anyone.
|
||||
|
||||
By default, Relay will automatically tag users from other networks with a suffix such as `/net`. This prevents confusing nick collisions if the same nick is used on multiple linked networks, and ensure that nicks from remote networks are all isolated into their own namespaces.
|
||||
|
||||
How is this relevant to an operator? It means that you **cannot ban users** using banmasks such as `*/net1!*@*`! The nick suffix is something PyLink adds artificially; on `net1`'s IRCd, which check the bans locally, the nick suffix doesn't exist and will therefore *not* match anyone.
|
||||
|
||||
### Services compatibility
|
||||
|
||||
While PyLink is generally able to run independently of individual networks' services, there are some gotchas. This list briefly details services features that have been known to cause problems with Relay. **Using any of these features in conjunction with Relay is *not* supported.**
|
||||
## Services compatibility
|
||||
While PyLink is generally able to run independently of individual networks's services, there are some gotchas. This list briefly details services features that have been known to cause problems with Relay. **Using any of these features in conjunction with Relay is *not* supported.**
|
||||
|
||||
- Anope, Atheme: **Clones prevention should be DISABLED** (or at a minimum, set to use G/KLINE instead of KILL)
|
||||
- Rationale: it is common for a person to want to connect to multiple networks in a Relay instance, because they are still independent entities. You can still use IRCd-side clones prevention, which sanely blocks connections instead of killing / banning everyone involved.
|
||||
@ -26,7 +21,6 @@ While PyLink is generally able to run independently of individual networks' serv
|
||||
- Rationale: ChanFix is incompatible with Relay CLAIM because it overrides ops on relay channels whenever they appear "opless". This basic op check is unable to consider the case of remote channel services not being set to join channels, and will instead cause join/message/part spam as CLAIM reverts the ChanFix service's mode changes.
|
||||
- *Any*: **Do NOT register a relayed channel on multiple networks**
|
||||
- Rationale: It is very easy for this to start kick or mode wars. (Bad akick mask? Secure ops enabled?)
|
||||
- Clientbot is an exception to this, though you may want to add Clientbot networks to CLAIM so that PyLink doesn't try to reverse modes set by services on the Clientbot network.
|
||||
- *Any*: **Do NOT jupe virtual Relay servers** (e.g. `net.relay`)
|
||||
- Rationale: This will just make PyLink split off - you should instead [delink any problem networks / channels](#dealing-with-disputes-and-emergencies).
|
||||
- Multiple PyLink Relay instances:
|
||||
@ -34,49 +28,35 @@ While PyLink is generally able to run independently of individual networks' serv
|
||||
- **Do NOT connect a network to 2+ separate PyLink instances if there is another network already acting as a hub for them**.
|
||||
- Not following these rules means that it's very easy for the Relay instances to go in a loop should an operator run the wrong command, which will hammer your CPU and relentlessly spam your channels.
|
||||
|
||||
Note: P10-specific services packages have not been particularly tested - your feedback is welcome.
|
||||
|
||||
## Relay commands
|
||||
The concept of relay channels in PyLink is greatly inspired by Janus, though with a few differences in command syntax.
|
||||
|
||||
The basic steps for setting up a relay is to first CREATE the channel with PyLink on the network that owns it, and run LINK from each network that wants to link to it. In most cases, you want to run CREATE on the network where the channel is registered with services.
|
||||
|
||||
Importantly, this means that CREATE and LINK have to be run on different networks for any particular channel, and that you should only run CREATE once for each distinct channel! This setup is intended to allow individual network admins to pick and choose channels they want to participate in.
|
||||
|
||||
First, to list all available channels:
|
||||
Then, to list all available channels:
|
||||
- `/msg PyLink linked`
|
||||
|
||||
To create a channel on Relay:
|
||||
To create a channel:
|
||||
- `/msg PyLink create #channelname`
|
||||
- Note: **you can only create channels on full IRCd links - this will NOT work with Clientbot.**
|
||||
- A channel created on a particular network is considered to be _owned_ by that network; this affects how CLAIM works for instance (see the next section)
|
||||
|
||||
To link to a channel already created on a different network:
|
||||
- `/msg PyLink link othernet #channelname`
|
||||
- You should replace `othernet` with the *short name* for the network that owns the channel.
|
||||
- Note: network names are case sensitive!
|
||||
|
||||
You can also link remote channels while using a different name for it on your network. (This is the third argument to the LINK command)
|
||||
You can also link remote channels to take a different name on your network. (This is the third argument to the LINK command)
|
||||
- `/msg PyLink link othernet #lobby #othernet-lobby`
|
||||
|
||||
To completely remove a relay channel (on the network that created it):
|
||||
To remove a relay channel that you've created:
|
||||
- `/msg PyLink destroy #channelname`
|
||||
|
||||
To delink a channel *linked to another network*:
|
||||
To delink a channel linked to another network:
|
||||
- `/msg PyLink delink #localchannelname`
|
||||
|
||||
To delink one of *your* channels from another network:
|
||||
- `/msg PyLink delink #yourchannelname <name-of-other-network>`
|
||||
|
||||
Then, to list all available channels:
|
||||
- `/msg PyLink linked`
|
||||
|
||||
### Claiming channels
|
||||
|
||||
Channel claiming is a feature which prevents oper override (MODE, KICK, TOPIC, KILL, OJOIN, ...) by other networks' operators from affecting your channels. By default, CLAIM is enabled for all new channels, though this can be configured via the [`relay::enable_default_claim` option](https://github.com/jlu5/PyLink/blob/3.0.0/example-conf.yml#L828-L831). Unless the claimed network list of a channel is _empty__, oper override will only be allowed from networks on the CLAIM list (plus the network that owns the channel).
|
||||
Channel claims are a feature which prevents oper override (MODE, KICK, TOPIC, KILL, OJOIN, ...) from working on channels not owned by or whitelisting a network. By default, CLAIM is enabled for all new channels, though this can be configured in PyLink 2.0+ via the [`relay::enable_default_claim` option](https://github.com/jlu5/PyLink/blob/2.0-beta1/example-conf.yml#L771-L774). Unless the claimed network list of a channel is EMPTY, oper override will only be allowed from networks on that list.
|
||||
|
||||
Note: these commands must be run from the network which owns the channel in question!
|
||||
|
||||
To set a claim:
|
||||
To set a claim (note: for these commands, you must be on the network which created the channel in question!):
|
||||
- `/msg PyLink claim #channel yournet,net2,net3` (the last parameter is a case-sensitive comma-separated list of networks)
|
||||
|
||||
To list claim networks on a channel:
|
||||
@ -86,8 +66,7 @@ To clear the claim list for a channel:
|
||||
- `/msg PyLink claim #channel -`
|
||||
|
||||
### Access control for links (LINKACL)
|
||||
|
||||
LINKACL allows you to allow or deny networks from linking to your channel. New channels are created using a blacklist by default, though this can be configured via the [`relay::linkacl_use_whitelist` option](https://github.com/jlu5/PyLink/blob/3.0.0/example-conf.yml#L823-L826).
|
||||
LINKACL allows you to blacklist or whitelist networks from linking to your channel. The default configuration enables blacklist mode by default, though this can be configured via the [`relay::linkacl_use_whitelist` option](https://github.com/jlu5/PyLink/blob/2.0-beta1/example-conf.yml#L766-L769).
|
||||
|
||||
To change between blacklist and whitelist mode:
|
||||
- `/msg PyLink linkacl whitelist #channel true/false`
|
||||
@ -97,13 +76,12 @@ To view the LINKACL networks for a channel:
|
||||
- `/msg PyLink linkacl #channel list`
|
||||
|
||||
To add a network to the whitelist **OR** remove a network from the blacklist:
|
||||
- `/msg PyLink linkacl #channel allow goodnet`
|
||||
- `/msg PyLink linkacl #channel allow badnet`
|
||||
|
||||
To remove a network from the whitelist **OR** add a network to the blacklist:
|
||||
- `/msg PyLink linkacl #channel deny badnet`
|
||||
- `/msg PyLink linkacl #channel deny goodnet`
|
||||
|
||||
### Adding channel descriptions
|
||||
|
||||
Starting with PyLink 2.0, you can annotate your channels with a description to use in LINKED:
|
||||
|
||||
To view the description for a channel:
|
||||
@ -117,17 +95,16 @@ To remove the description for a channel:
|
||||
|
||||
## Dealing with disputes and emergencies
|
||||
|
||||
The best thing to do in the event of a dispute is to delink the problem networks / channels. In order for individual networks to maintain their autonomy, KILLs and network bans (K/G/ZLINE) will most often *not* behave the way you expect them to.
|
||||
The best thing to do in the event of a dispute is to delink the problem networks / channels. KILLs and network bans (K/G/ZLINE) will most often *not* behave the way you expect it to.
|
||||
|
||||
### Kill handling
|
||||
Special kill handling was introduced in PyLink 2.0, while in previous versions they were always rejected:
|
||||
|
||||
Special kill handling was introduced in PyLink 2.0, while in previous versions they were always bounced:
|
||||
|
||||
1) If the sender was a server and not a client, reject the kill. (This prevents services messups from wreaking havoc across the relay)
|
||||
2) If the target and source networks share a [kill share pool](https://github.com/jlu5/PyLink/blob/3.0.0/example-conf.yml#L782-L792), relay the kill as-is.
|
||||
3) Otherwise, check every channel that the kill target is in:
|
||||
- If the sender is opped or has claim access in a channel, forward the KILL as a kick in that channel.
|
||||
- Otherwise, bounce the kill silently (i.e. rejoin the user immediately).
|
||||
1) If the sender was a server and not a client, reject the kill.
|
||||
2) If the target and source networks are both in a(ny) [kill share pool](https://github.com/jlu5/PyLink/blob/2.0-beta1/example-conf.yml#L725-L735), relay the kill as-is.
|
||||
3) Otherwise, check every channels the kill target is in:
|
||||
- If the killer has claim access in a channel, forward the KILL as a kick to that channel.
|
||||
- Otherwise, bounce the kill silently.
|
||||
|
||||
### Network bans (K/G/ZLINE)
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# PyLink hooks reference
|
||||
|
||||
***Last updated for 3.1-dev (2021-06-13).***
|
||||
***Last updated for 2.1-alpha2 (2019-07-01).***
|
||||
|
||||
In PyLink, protocol modules communicate with plugins through a system of hooks. This has the benefit of being IRCd-independent, allowing most plugins to function regardless of the IRCd being used.
|
||||
Each hook payload is formatted as a Python `list`, with three arguments: `numeric`, `command`, and `args`.
|
||||
@ -19,11 +19,11 @@ The command `:42XAAAAAB PRIVMSG #dev :test` would result in the following raw ho
|
||||
|
||||
- `['42XAAAAAB', 'PRIVMSG', {'target': '#dev', 'text': 'test', 'ts': 1451174041}]`
|
||||
|
||||
On UnrealIRCd, because SETHOST is mapped to CHGHOST, `:jlu5 SETHOST blah` would return the raw hook data of this (with the nick converted into UID automatically by the protocol module):
|
||||
On UnrealIRCd, because SETHOST is mapped to CHGHOST, `:GL SETHOST blah` would return the raw hook data of this (with the nick converted into UID automatically by the protocol module):
|
||||
|
||||
- `['001ZJZW01', 'CHGHOST', {'ts': 1451174512, 'target': '001ZJZW01', 'newhost': 'blah'}]`
|
||||
|
||||
Some hooks, like MODE, are more complex and can include the entire state of a channel. This will be further described later. `:jlu5 MODE #chat +o PyLink-devel` is converted into (pretty-printed for readability):
|
||||
Some hooks, like MODE, are more complex and can include the entire state of a channel. This will be further described later. `:GL MODE #chat +o PyLink-devel` is converted into (pretty-printed for readability):
|
||||
|
||||
```
|
||||
['001ZJZW01',
|
||||
@ -100,10 +100,9 @@ The following hooks represent regular IRC commands sent between servers.
|
||||
- `oldtopic` denotes the original topic, and `text` indicates the new one being set.
|
||||
- `setter` is the raw sender field given to us by the IRCd; it may be a `nick!user@host`, a UID, a SID, a server name, or a nick. This is not processed at the protocol level.
|
||||
|
||||
- **UID**: `{'uid': 'UID1', 'ts': 1234567891, 'nick': 'supercoder', 'realhost': 'localhost', 'host': 'admin.testnet.local', 'ident': ident, 'ip': '127.0.0.1', 'secure': True}`
|
||||
- **UID**: `{'uid': 'UID1', 'ts': 1234567891, 'nick': 'supercoder', 'realhost': 'localhost', 'host': 'admin.testnet.local', 'ident': ident, 'ip': '127.0.0.1'}`
|
||||
- This command is used to introduce users; the sender of the message should be the server bursting or announcing the connection.
|
||||
- `ts` refers to the user's signon time.
|
||||
- `secure` is a ternary value (True/False/None) that determines whether the user is connected over a secure connection (SSL/TLS). This value is only available on some IRCds: currently UnrealIRCd, P10, Charybdis TS6, and Hybrid; on other servers this will be `None`.
|
||||
|
||||
### Extra commands (where supported)
|
||||
|
||||
@ -164,17 +163,15 @@ At this time, commands that are handled by protocol modules without returning an
|
||||
|
||||
## Changes
|
||||
|
||||
* 2021-06-13 (3.1-dev)
|
||||
- Added the `secure` field to `UID` hooks.
|
||||
* 2019-07-01 (2.1-alpha2)
|
||||
- KILL and QUIT hooks now always include a non-empty `userdata` key. Now, if a QUIT message for a killed user is received before the corresponding KILL (or vice versa), only the first message received will have the corresponding hook payload broadcasted.
|
||||
- KILL and QUIT hooks now always include a non-empty `userdata` key. Now, if a QUIT message for a killed user is received before the corresponding KILL (or vice versa), only the first message received will have the corresponding hook payload broadcasted.
|
||||
* 2018-12-27 (2.1-dev)
|
||||
- Add the `affected_servers` argument to SQUIT hooks.
|
||||
- Add the `affected_servers` argument to SQUIT hooks.
|
||||
* 2018-07-11 (2.0.0)
|
||||
- Version bump for 2.0 stable release; no meaningful content changes.
|
||||
- Version bump for 2.0 stable release; no meaningful content changes.
|
||||
* 2018-01-13 (2.0-alpha2)
|
||||
- Replace `IrcChannel`, `IrcUser`, and `IrcServer` with their new class names (`classes.Channel`, `classes.User`, and `classes.Server`)
|
||||
- Replace `irc.fullVersion()` with `irc.version()`
|
||||
- Various minor wording tweaks.
|
||||
- Replace `IrcChannel`, `IrcUser`, and `IrcServer` with their new class names (`classes.Channel`, `classes.User`, and `classes.Server`)
|
||||
- Replace `irc.fullVersion()` with `irc.version()`
|
||||
- Various minor wording tweaks.
|
||||
* 2017-02-24 (1.2-dev)
|
||||
- The `was_successful` key was added to PYLINK_DISCONNECT.
|
||||
- The `was_successful` key was added to PYLINK_DISCONNECT.
|
||||
|
@ -1,6 +1,6 @@
|
||||
# PyLink Protocol Module Specification
|
||||
|
||||
***Last updated for 3.1-dev (2021-06-15).***
|
||||
***Last updated for 2.1-alpha2 (2019-11-02).***
|
||||
|
||||
Starting with PyLink 2.x, a *protocol module* is any module containing a class derived from `PyLinkNetworkCore` (e.g. `InspIRCdProtocol`), along with a global `Class` attribute set equal to it (e.g. `Class = InspIRCdProtocol`). These modules do everything from managing connections to providing plugins with an API to send and receive data. New protocol modules may be implemented based off any of the classes in the following inheritance tree, with each containing a different amount of abstraction.
|
||||
|
||||
@ -38,9 +38,8 @@ This class offers the most flexibility because the protocol module can choose ho
|
||||
### `protocols.ircs2s_common.IRCS2SProtocol`
|
||||
`IRCS2SProtocol` is the most complete base server class, including a generic `handle_events()` supporting most IRC S2S message styles (i.e. prefix-less messages, protocols with and without UIDs). It also defines some incoming and outgoing command functions that hardly vary between protocols: `invite()`, `kick()`, `message()`, `notice()`, `numeric()`, `part()`, `quit()`, `squit()`, and `topic()` as of PyLink 2.0. This list is subject to change in future releases.
|
||||
|
||||
### `classes.PyLinkNetworkCoreWithUtils`
|
||||
|
||||
`PyLinkNetworkCoreWithUtils` contains various state checking and IRC-related utility functions. Originally this abstraction was intended to support non-IRC protocols (Discord, Telegram, Slack, ...), but I (jlu5) no longer support this as a development focus. The main reason being is that in order to keep track of IRC server state correctly, PyLink makes a lot of assumptions specific to IRC (e.g. explicit join/part, mode formats, etc.). Trying to reconcile this with other platforms is a large undertaking and ideally requires a different, more generic protocol specification. (In PyLink 2.x there was a [Discord module](https://github.com/PyLink/pylink-discord) that is no longer supported - see https://jlu5.com/blog/the-trouble-with-pylink for a more in depth explanation as to why.)
|
||||
### For non-IRC protocols: `classes.PyLinkNetworkCoreWithUtils`
|
||||
Although no such transports have been implemented yet, PyLink leaves some level of abstraction for non-IRC protocols (e.g. Discord, Telegram, Slack, ...) by providing generic classes that only include state checking and utility functions.
|
||||
|
||||
Subclassing one of the `PyLinkNetworkCore*` classes means that a protocol module only needs to define one method of entry: `connect()`, and must set up its own message handling stack. Protocol configuration validation checks and autoconnect must also be reimplemented. IRC-style utility functions (i.e. `PyLinkNetworkCoreWithUtils` methods) should also be reimplemented / overridden when applicable.
|
||||
|
||||
@ -83,8 +82,6 @@ Unless otherwise noted, the camel-case variants of command functions (e.g. "`spa
|
||||
|
||||
- **`nick`**`(self, source, newnick)` - Changes the nick of a PyLink client.
|
||||
|
||||
- **`oper_notice`**`(self, source, target)` - Sends a notice to all operators on the network.
|
||||
|
||||
- **`notice`**`(self, source, target, text)` - Sends a NOTICE from a PyLink client or server.
|
||||
|
||||
- **`numeric`**`(self, source, numeric, target, text)` - Sends a raw numeric `numeric` with `text` from the `source` server to `target`. This should raise `NotImplementedError` if not supported on a protocol.
|
||||
@ -210,7 +207,7 @@ Modes are stored not stored as strings, but lists of mode pairs in order to ease
|
||||
|
||||
- `self.parse_modes('#chat', ['+ol invalidnick'])`:
|
||||
- `[]`
|
||||
- `self.parse_modes('#chat', ['+o jlu5'])`:
|
||||
- `self.parse_modes('#chat', ['+o GLolol'])`:
|
||||
- `[('+o', '001ZJZW01')]`
|
||||
|
||||
Afterwords, a parsed mode list can be applied to channel name or UID using `self.apply_modes(target, parsed_modelist)`.
|
||||
@ -220,16 +217,16 @@ Afterwords, a parsed mode list can be applied to channel name or UID using `self
|
||||
Internally, modes are stored in `Channel` and `User` objects as sets, **with the `+` prefixing each mode character omitted**. These sets are accessed via the `modes` attribute:
|
||||
|
||||
```
|
||||
<+jlu5> PyLink-devel, eval irc.users[source].modes
|
||||
<+GLolol> PyLink-devel, eval irc.users[source].modes
|
||||
<@PyLink-devel> {('i', None), ('x', None), ('w', None), ('o', None)}
|
||||
<+jlu5> PyLink-devel, eval irc.channels['#chat'].modes
|
||||
<+GLolol> PyLink-devel, eval irc.channels['#chat'].modes
|
||||
<@PyLink-devel> {('n', None), ('t', None)}
|
||||
```
|
||||
|
||||
**Exception**: the owner, admin, op, halfop, and voice channel prefix modes are stored separately as a dict of sets in `Channel.prefixmodes`:
|
||||
|
||||
```
|
||||
<@jlu5> PyLink-devel, eval irc.channels['#chat'].prefixmodes
|
||||
<@GLolol> PyLink-devel, eval irc.channels['#chat'].prefixmodes
|
||||
<+PyLink-devel> {'op': set(), 'halfop': set(), 'voice': {'38QAAAAAA'}, 'owner': set(), 'admin': set()}
|
||||
```
|
||||
|
||||
@ -266,9 +263,6 @@ In short, protocol modules have some very important jobs. If any of these aren't
|
||||
7) Declare the correct set of protocol module capabilities to prevent confusing PyLink's plugins.
|
||||
|
||||
## Changes to this document
|
||||
* 2021-06-15 (3.1-dev)
|
||||
- Added `oper_notice()` function to send notices to opers (GLOBOPS / OPERWALL on most IRCds)
|
||||
- Update notes about non-IRC protocols and PyLinkNetworkCoreWithUtils
|
||||
* 2019-11-02 (2.1-beta1)
|
||||
- Added protocol capability: `can-manage-bot-channels`
|
||||
* 2019-10-10 (2.1-beta1)
|
||||
|
@ -86,14 +86,6 @@ pylink:
|
||||
# This defaults to False if not set.
|
||||
#join_empty_channels: false
|
||||
|
||||
# Determines where the plugins write their databases. The path can be relative to the directory
|
||||
# PyLink is run from. Defaults to the current directory.
|
||||
#data_dir: ""
|
||||
|
||||
# Determines where the PID file is written. The path can be relative to the directory PyLink is
|
||||
# run from. Defaults to the current directory.
|
||||
#pid_dir: ""
|
||||
|
||||
login:
|
||||
# NOTE: for users migrating from PyLink < 1.1, the old login:user/login:password settings
|
||||
# have been deprecated. We strongly recommend migrating to the new "accounts:" block below, as
|
||||
@ -204,9 +196,12 @@ servers:
|
||||
|
||||
# InspIRCd specific option: sets the target InspIRCd protocol version.
|
||||
# Valid values include:
|
||||
# "insp3" - InspIRCd 3.x (1205) [DEFAULT]
|
||||
# "insp20" - InspIRCd 2.0.x (1202) [legacy, deprecated]
|
||||
# "insp20" - InspIRCd 2.0.x (1202) [DEFAULT†]
|
||||
# "insp3" - InspIRCd 3.x (1205) [BETA]
|
||||
#target_version: insp20
|
||||
#target_version: insp3
|
||||
# † InspIRCd 2.0 servers can link to InspIRCd 3.0 via built-in link compatibility, but some
|
||||
# new features may not work correctly.
|
||||
|
||||
# Sets the max nick length for the network. It is important that this is set correctly, or
|
||||
# PyLink might introduce a nick that is too long and cause netsplits!
|
||||
@ -268,6 +263,7 @@ servers:
|
||||
# https://docs.python.org/3/library/codecs.html#standard-encodings
|
||||
# Changing this setting requires a disconnect and reconnect of the corresponding network
|
||||
# to apply.
|
||||
# This setting is EXPERIMENTAL as of PyLink 1.2.x.
|
||||
#encoding: utf-8
|
||||
|
||||
# Sets the ping frequency in seconds (i.e. how long we should wait between sending pings to
|
||||
@ -305,6 +301,13 @@ servers:
|
||||
ip: ::1
|
||||
port: 8067
|
||||
|
||||
# Determines whether IPv6 should be used for this connection. Should the ip:
|
||||
# above be a hostname instead of an IP, this will also affect whether A records
|
||||
# (IPv4) or AAAA records (IPv6) will be used in resolving it.
|
||||
# As of PyLink 2.0-beta1, you can leave this unset for direct connections to IP addresses;
|
||||
# the address type will be automatically detected.
|
||||
#ipv6: yes
|
||||
|
||||
# Received and sent passwords. For passwordless links using SSL fingerprints, simply set
|
||||
# these two fields to "*" and enable SSL with a cert and key file.
|
||||
recvpass: "coffee"
|
||||
@ -338,13 +341,9 @@ servers:
|
||||
port: 45454
|
||||
|
||||
# When the IP field is set to a hostname, the "ipv6" option determines whether IPv4 or IPv6
|
||||
# addresses should be used when resolving it.
|
||||
# As of PyLink 3.1, this defaults to null, falling back to the system's default preferences
|
||||
# if not set (e.g. /etc/gai.conf on Linux). Previous versions default to making IPv4 connections only.
|
||||
# This option is overridden by "bindhost" if it is also provided.
|
||||
#ipv6: null
|
||||
|
||||
# Specifies the IP to make outgoing connections from, for multi-homed hosts.
|
||||
# addresses should be used when resolving it. You can leave this field blank and use an
|
||||
# explicit bindhost instead, which will let the the address type be automatically detected.
|
||||
#ipv6: false
|
||||
#bindhost: 1111:2222:3333:4444
|
||||
|
||||
# Note: if you are actually using dynamic DNS for an IRC link, consider enabling
|
||||
@ -651,22 +650,18 @@ logging:
|
||||
"#services":
|
||||
loglevel: INFO
|
||||
|
||||
# The directory where the log files are written. The path can be relative to the directory
|
||||
# PyLink is run from. Defaults to "log/".
|
||||
#log_dir: "log"
|
||||
|
||||
files:
|
||||
# Logs to file targets. These will be placed in the folder specified by log_dir with a
|
||||
# filename based on the current instance name and the target name:
|
||||
# instancename-targetname.log
|
||||
# Logs to file targets. These will be placed in the log/ folder in the
|
||||
# PyLink directory, with a filename based on the current instance name
|
||||
# and the target name: instancename-targetname.log
|
||||
|
||||
# When running with "pylink", this will create log/pylink-errors.log
|
||||
# When running with "pylink someconf.yml", this will create log/someconf-errors.log
|
||||
# When running with ./pylink, this will create log/pylink-errors.log
|
||||
# When running with ./pylink someconf.yml, this will create log/someconf-errors.log
|
||||
"errors":
|
||||
loglevel: ERROR
|
||||
|
||||
# Ditto above. When running with "pylink", it will use log/pylink-commands.log
|
||||
# When running with "pylink someconf.yml", this will create log/someconf-commands.log
|
||||
# Ditto above. When running with ./pylink, it will use log/pylink-commands.log
|
||||
# When running with ./pylink someconf.yml, this will create log/someconf-commands.log
|
||||
"commands":
|
||||
loglevel: INFO
|
||||
|
||||
|
@ -25,8 +25,7 @@ def _main():
|
||||
|
||||
# Write and check for an existing PID file unless specifically told not to.
|
||||
if not args.no_pid:
|
||||
pid_dir = conf.conf['pylink'].get('pid_dir', '')
|
||||
pidfile = os.path.join(pid_dir, '%s.pid' % conf.confname)
|
||||
pidfile = '%s.pid' % conf.confname
|
||||
pid_exists = False
|
||||
pid = None
|
||||
if os.path.exists(pidfile):
|
||||
|
15
log.py
15
log.py
@ -12,11 +12,12 @@ import os
|
||||
|
||||
from . import conf, world
|
||||
|
||||
__all__ = ['log']
|
||||
|
||||
# Stores a list of active file loggers.
|
||||
fileloggers = []
|
||||
|
||||
logdir = os.path.join(os.getcwd(), 'log')
|
||||
os.makedirs(logdir, exist_ok=True)
|
||||
|
||||
# TODO: perhaps make this format configurable?
|
||||
_format = '%(asctime)s [%(levelname)s] %(message)s'
|
||||
logformatter = logging.Formatter(_format)
|
||||
@ -46,19 +47,11 @@ def _make_file_logger(filename, level=None):
|
||||
"""
|
||||
Initializes a file logging target with the given filename and level.
|
||||
"""
|
||||
logconf = conf.conf.get('logging', {})
|
||||
|
||||
logdir = logconf.get('log_dir')
|
||||
if logdir is None:
|
||||
logdir = os.path.join(os.getcwd(), 'log')
|
||||
|
||||
os.makedirs(logdir, exist_ok=True)
|
||||
|
||||
# Use log names specific to the current instance, to prevent multiple
|
||||
# PyLink instances from overwriting each others' log files.
|
||||
target = os.path.join(logdir, '%s-%s.log' % (conf.confname, filename))
|
||||
|
||||
logrotconf = logconf.get('filerotation', {})
|
||||
logrotconf = conf.conf.get('logging', {}).get('filerotation', {})
|
||||
|
||||
# Max amount of bytes per file, before rotation is done. Defaults to 20 MiB.
|
||||
maxbytes = logrotconf.get('max_bytes', 20971520)
|
||||
|
@ -1,6 +1,6 @@
|
||||
# antispam.py: Basic services-side spamfilters for IRC
|
||||
|
||||
from pylinkirc import conf, utils
|
||||
from pylinkirc import conf, utils, world
|
||||
from pylinkirc.log import log
|
||||
|
||||
mydesc = ("Provides anti-spam functionality.")
|
||||
|
@ -4,6 +4,7 @@ with things.
|
||||
"""
|
||||
from pylinkirc import utils
|
||||
from pylinkirc.coremods import permissions
|
||||
from pylinkirc.log import log
|
||||
|
||||
|
||||
@utils.add_cmd
|
||||
|
@ -1,10 +1,10 @@
|
||||
# commands.py: base PyLink commands
|
||||
import sys
|
||||
import time
|
||||
|
||||
from pylinkirc import __version__, conf, real_version, utils, world
|
||||
from pylinkirc.coremods import permissions
|
||||
from pylinkirc.coremods.login import pwd_context
|
||||
from pylinkirc.log import log
|
||||
|
||||
default_permissions = {"*!*@*": ['commands.status', 'commands.showuser', 'commands.showchan', 'commands.shownet']}
|
||||
|
||||
@ -260,8 +260,7 @@ def version(irc, source, args):
|
||||
"""takes no arguments.
|
||||
|
||||
Returns the version of the currently running PyLink instance."""
|
||||
py_version = utils.NORMALIZEWHITESPACE_RE.sub(' ', sys.version)
|
||||
irc.reply("PyLink version \x02%s\x02 (in VCS: %s), running on Python %s." % (__version__, real_version, py_version))
|
||||
irc.reply("PyLink version \x02%s\x02 (in VCS: %s), released under the Mozilla Public License version 2.0." % (__version__, real_version))
|
||||
irc.reply("The source of this program is available at \x02%s\x02." % world.source)
|
||||
|
||||
@utils.add_cmd
|
||||
|
@ -1,12 +1,16 @@
|
||||
"""
|
||||
exec.py: Provides commands for executing raw code and debugging PyLink.
|
||||
"""
|
||||
import importlib
|
||||
import pprint
|
||||
# These imports are not strictly necessary, but make the following modules
|
||||
# easier to access through eval and exec.
|
||||
import re
|
||||
import threading
|
||||
import time
|
||||
|
||||
from pylinkirc import utils, world, conf
|
||||
import pylinkirc
|
||||
from pylinkirc import *
|
||||
from pylinkirc.coremods import permissions
|
||||
from pylinkirc.log import log
|
||||
|
||||
@ -14,11 +18,6 @@ exec_locals_dict = {}
|
||||
PPRINT_MAX_LINES = 20
|
||||
PPRINT_WIDTH = 200
|
||||
|
||||
if not conf.conf['pylink'].get("debug_enabled", False):
|
||||
raise RuntimeError("pylink::debug_enabled must be enabled to load this plugin. "
|
||||
"This should ONLY be used in test environments for debugging and development, "
|
||||
"as anyone with access to this plugin's commands can run arbitrary code as the PyLink user!")
|
||||
|
||||
def _exec(irc, source, args, locals_dict=None):
|
||||
"""<code>
|
||||
|
||||
|
@ -4,6 +4,7 @@ games.py: Creates a bot providing a few simple games.
|
||||
import random
|
||||
|
||||
from pylinkirc import utils
|
||||
from pylinkirc.log import log
|
||||
|
||||
mydesc = "The \x02Games\x02 plugin provides simple games for IRC."
|
||||
|
||||
|
@ -28,8 +28,7 @@ def g(irc, source, args):
|
||||
netcount = 0
|
||||
chancount = 0
|
||||
for netname, ircd in world.networkobjects.items():
|
||||
# Skip networks that aren't ready and dummy networks which don't have .pseudoclient set
|
||||
if ircd.connected.is_set() and ircd.pseudoclient:
|
||||
if ircd.connected.is_set(): # Only attempt to send to connected networks
|
||||
netcount += 1
|
||||
for channel in ircd.pseudoclient.channels:
|
||||
|
||||
@ -37,7 +36,7 @@ def g(irc, source, args):
|
||||
|
||||
skip = False
|
||||
for exempt in local_exempt_channels:
|
||||
if ircd.match_text(exempt, str(channel)):
|
||||
if irc.match_text(exempt, channel):
|
||||
log.debug('global: Skipping channel %s%s for exempt %r', netname, channel, exempt)
|
||||
skip = True
|
||||
break
|
||||
|
@ -406,7 +406,14 @@ def kill(irc, source, args):
|
||||
|
||||
userdata = irc.users.get(targetu)
|
||||
|
||||
reason = "Requested by %s: %s" % (irc.get_friendly_name(source), reason)
|
||||
# Deliver a more complete kill reason if our target is a non-PyLink client.
|
||||
# We skip this for PyLink clients so that relayed kills don't get
|
||||
# "Killed (abc (...))" tacked on both here and by the receiving IRCd.
|
||||
if not irc.is_internal_client(targetu):
|
||||
reason = "Killed (%s (Requested by %s: %s))" % (
|
||||
irc.get_friendly_name(sender),
|
||||
irc.get_friendly_name(source),
|
||||
reason)
|
||||
|
||||
irc.kill(sender, targetu, reason)
|
||||
|
||||
|
@ -4,7 +4,7 @@ raw.py: Provides a 'raw' command for sending raw text to IRC.
|
||||
from pylinkirc import utils
|
||||
from pylinkirc.coremods import permissions
|
||||
from pylinkirc.log import log
|
||||
from pylinkirc import conf
|
||||
|
||||
|
||||
@utils.add_cmd
|
||||
def raw(irc, source, args):
|
||||
@ -12,12 +12,15 @@ def raw(irc, source, args):
|
||||
|
||||
Sends raw text to the IRC server.
|
||||
|
||||
Use with caution - This command is only officially supported on Clientbot networks."""
|
||||
if not conf.conf['pylink'].get("raw_enabled", False):
|
||||
raise RuntimeError("Raw commands are not supported on this protocol")
|
||||
This command is not officially supported on non-Clientbot networks, where it
|
||||
requires a separate permission."""
|
||||
|
||||
# exec.raw is included for backwards compatibility with PyLink 1.x
|
||||
permissions.check_permissions(irc, source, ['raw.raw', 'exec.raw'])
|
||||
if irc.protoname == 'clientbot':
|
||||
# exec.raw is included for backwards compatibility with PyLink 1.x
|
||||
perms = ['raw.raw', 'exec.raw']
|
||||
else:
|
||||
perms = ['raw.raw.unsupported_network']
|
||||
permissions.check_permissions(irc, source, perms)
|
||||
|
||||
args = ' '.join(args)
|
||||
if not args.strip():
|
||||
|
136
plugins/relay.py
136
plugins/relay.py
@ -16,7 +16,7 @@ RELAY_UNLOADED_MSG = "Relay plugin unloaded."
|
||||
try:
|
||||
import cachetools
|
||||
except ImportError as e:
|
||||
raise ImportError("PyLink Relay requires cachetools as of PyLink 3.0: https://pypi.org/project/cachetools/") from e
|
||||
raise ImportError("PyLink Relay requires cachetools as of PyLink 2.1: https://pypi.org/project/cachetools/") from e
|
||||
|
||||
try:
|
||||
import unidecode
|
||||
@ -129,7 +129,7 @@ def die(irc=None):
|
||||
except KeyError:
|
||||
log.debug('relay.die: failed to clear persistent channels:', exc_info=True)
|
||||
|
||||
IRC_ASCII_ALLOWED_CHARS = string.digits + string.ascii_letters + '^|\\-_[]{}`'
|
||||
IRC_ASCII_ALLOWED_CHARS = string.digits + string.ascii_letters + '/^|\\-_[]{}`'
|
||||
FALLBACK_SEPARATOR = '|'
|
||||
FALLBACK_CHARACTER = '-'
|
||||
|
||||
@ -215,7 +215,7 @@ def normalize_nick(irc, netname, nick, times_tagged=0, uid=''):
|
||||
# Loop over every character in the nick, making sure that it only contains valid
|
||||
# characters.
|
||||
if not is_unicode_capable:
|
||||
nick = _sanitize(nick, extrachars='/')
|
||||
nick = _sanitize(nick)
|
||||
else:
|
||||
# UnrealIRCd 4's forbidden nick chars, from
|
||||
# https://github.com/unrealircd/unrealircd/blob/02d69e7d8/src/modules/charsys.c#L152-L163
|
||||
@ -236,7 +236,7 @@ def normalize_nick(irc, netname, nick, times_tagged=0, uid=''):
|
||||
|
||||
# Track how many times the given nick has been tagged. If this is 0, no tag is used.
|
||||
# If this is 1, a /network tag is added. Otherwise, keep adding one character to the
|
||||
# separator: jlu5 -> jlu5/net1 -> jlu5//net1 -> ...
|
||||
# separator: GLolol -> GLolol/net1 -> GLolol//net1 -> ...
|
||||
if times_tagged >= 1:
|
||||
suffix = "%s%s%s" % (separator[0]*times_tagged, separator[1:], netname)
|
||||
allowedlength -= len(suffix)
|
||||
@ -407,12 +407,6 @@ def spawn_relay_user(irc, remoteirc, user, times_tagged=0, reuse_sid=None):
|
||||
# Truncate idents at 10 characters, because TS6 won't like them otherwise!
|
||||
ident = ident[:10]
|
||||
|
||||
# HACK: hybrid will reject idents that start with a symbol
|
||||
if remoteirc.protoname == 'hybrid':
|
||||
goodchars = tuple(string.ascii_letters + string.digits + '~')
|
||||
if not ident.startswith(goodchars):
|
||||
ident = 'r' + ident
|
||||
|
||||
# Normalize hostnames
|
||||
host = normalize_host(remoteirc, userobj.host)
|
||||
realname = userobj.realname
|
||||
@ -828,7 +822,7 @@ def relay_joins(irc, channel, users, ts, targetirc=None, **kwargs):
|
||||
"""
|
||||
|
||||
log.debug('(%s) relay.relay_joins: called on %r with users %r, targetirc=%s', irc.name, channel,
|
||||
users, targetirc)
|
||||
['%s/%s' % (user, irc.get_friendly_name(user)) for user in users], targetirc)
|
||||
|
||||
if ts < 750000:
|
||||
current_ts = int(time.time())
|
||||
@ -1357,7 +1351,7 @@ def handle_join(irc, numeric, command, args):
|
||||
modes = []
|
||||
for user in users:
|
||||
# XXX: Find the diff of the new and old mode lists of the channel. Not pretty, but I'd
|
||||
# rather not change the 'users' format of SJOIN just for this. -jlu5
|
||||
# rather not change the 'users' format of SJOIN just for this. -GL
|
||||
try:
|
||||
oldmodes = set(chandata.get_prefix_modes(user))
|
||||
except KeyError:
|
||||
@ -1521,9 +1515,6 @@ def handle_messages(irc, numeric, command, args):
|
||||
log.debug('(%s) relay.handle_messages: dropping PM from server %s to %s',
|
||||
irc.name, numeric, target)
|
||||
return
|
||||
elif not irc.has_cap('can-spawn-clients') and not world.plugins.get('relay_clientbot'):
|
||||
# For consistency, only read messages from clientbot networks if relay_clientbot is loaded
|
||||
return
|
||||
|
||||
relay = get_relay(irc, target)
|
||||
remoteusers = relayusers[(irc.name, numeric)]
|
||||
@ -2070,7 +2061,7 @@ def handle_kill(irc, numeric, command, args):
|
||||
|
||||
# Then, forward to the home network.
|
||||
hsender = get_relay_server_sid(origirc, irc, spawn_if_missing=False) or \
|
||||
origirc.sid
|
||||
homeirc.sid
|
||||
log.debug('(%s) relay.handle_kill: forwarding kill to %s/%s@%s as '
|
||||
'kick on %s', irc.name, realuser[1], target_nick,
|
||||
realuser[0], homechan)
|
||||
@ -2374,11 +2365,11 @@ def destroy(irc, source, args):
|
||||
|
||||
Removes the given channel from the PyLink Relay, delinking all networks linked to it. If the home network is given and you are logged in as admin, this can also remove relay channels from other networks."""
|
||||
try: # Two args were given: first one is network name, second is channel.
|
||||
channel = args[1]
|
||||
channel = irc.to_lower(args[1])
|
||||
network = args[0]
|
||||
except IndexError:
|
||||
try: # One argument was given; assume it's just the channel.
|
||||
channel = args[0]
|
||||
channel = irc.to_lower(args[0])
|
||||
network = irc.name
|
||||
except IndexError:
|
||||
irc.error("Not enough arguments. Needs 1-2: channel, network (optional).")
|
||||
@ -2395,11 +2386,7 @@ def destroy(irc, source, args):
|
||||
else:
|
||||
permissions.check_permissions(irc, source, ['relay.destroy.remote'])
|
||||
|
||||
# Allow deleting old channels if the local network's casemapping ever changes
|
||||
if (network, channel) in db:
|
||||
entry = (network, channel)
|
||||
else:
|
||||
entry = (network, irc.to_lower(channel))
|
||||
entry = (network, channel)
|
||||
if entry in db:
|
||||
stop_relay(entry)
|
||||
del db[entry]
|
||||
@ -2460,12 +2447,8 @@ def link(irc, source, args):
|
||||
|
||||
args = link_parser.parse_args(args)
|
||||
|
||||
# Normalize channel case. For the target channel it's possible for the local and remote casemappings
|
||||
# to differ - if we find the unnormalized channel name in the list, we should just use that.
|
||||
# This mainly affects channels with e.g. | in them.
|
||||
channel_orig = str(args.channel)
|
||||
channel_norm = irc.to_lower(channel_orig)
|
||||
|
||||
# Normalize channel case
|
||||
channel = irc.to_lower(str(args.channel))
|
||||
localchan = irc.to_lower(str(args.localchannel or args.channel))
|
||||
remotenet = args.remotenet
|
||||
|
||||
@ -2509,61 +2492,58 @@ def link(irc, source, args):
|
||||
irc.error('Channel %r is already part of a relay.' % localchan)
|
||||
return
|
||||
|
||||
if (remotenet, channel_orig) in db:
|
||||
channel = channel_orig
|
||||
elif (remotenet, channel_norm) in db:
|
||||
channel = channel_norm
|
||||
else:
|
||||
try:
|
||||
entry = db[(remotenet, channel)]
|
||||
except KeyError:
|
||||
irc.error('No such relay %r exists.' % args.channel)
|
||||
return
|
||||
entry = db[(remotenet, channel)]
|
||||
|
||||
whitelist_mode = entry.get('use_whitelist', False)
|
||||
if ((not whitelist_mode) and irc.name in entry['blocked_nets']) or \
|
||||
(whitelist_mode and irc.name not in entry.get('allowed_nets', set())):
|
||||
irc.error('Access denied (target channel is not open to links).')
|
||||
log.warning('(%s) relay: Blocking link request %s%s -> %s%s from %s due to LINKACL (whitelist_mode=%s)',
|
||||
irc.name, irc.name, localchan, remotenet, channel,
|
||||
irc.get_hostmask(source), whitelist_mode)
|
||||
return
|
||||
for link in entry['links']:
|
||||
if link[0] == irc.name:
|
||||
irc.error("Remote channel '%s%s' is already linked here "
|
||||
"as %r." % (remotenet, args.channel, link[1]))
|
||||
return
|
||||
|
||||
if args.force_ts:
|
||||
permissions.check_permissions(irc, source, ['relay.link.force_ts', 'relay.link.force'])
|
||||
log.info("(%s) relay: Forcing link %s%s -> %s%s", irc.name, irc.name, localchan, remotenet,
|
||||
args.channel)
|
||||
else:
|
||||
if not world.networkobjects[remotenet].connected.is_set():
|
||||
log.debug('(%s) relay: Blocking link request %s%s -> %s%s because the target '
|
||||
'network is down', irc.name, irc.name, localchan, remotenet, args.channel)
|
||||
irc.error("The target network %s is not connected; refusing to link (you may be "
|
||||
"able to override this with the --force option)." % remotenet)
|
||||
whitelist_mode = entry.get('use_whitelist', False)
|
||||
if ((not whitelist_mode) and irc.name in entry['blocked_nets']) or \
|
||||
(whitelist_mode and irc.name not in entry.get('allowed_nets', set())):
|
||||
irc.error('Access denied (target channel is not open to links).')
|
||||
log.warning('(%s) relay: Blocking link request %s%s -> %s%s from %s due to LINKACL (whitelist_mode=%s)',
|
||||
irc.name, irc.name, localchan, remotenet, channel,
|
||||
irc.get_hostmask(source), whitelist_mode)
|
||||
return
|
||||
for link in entry['links']:
|
||||
if link[0] == irc.name:
|
||||
irc.error("Remote channel '%s%s' is already linked here "
|
||||
"as %r." % (remotenet, args.channel, link[1]))
|
||||
return
|
||||
|
||||
our_ts = irc.channels[localchan].ts
|
||||
if channel not in world.networkobjects[remotenet].channels:
|
||||
irc.error("Unknown target channel %r." % channel)
|
||||
return
|
||||
if args.force_ts:
|
||||
permissions.check_permissions(irc, source, ['relay.link.force_ts', 'relay.link.force'])
|
||||
log.info("(%s) relay: Forcing link %s%s -> %s%s", irc.name, irc.name, localchan, remotenet,
|
||||
args.channel)
|
||||
else:
|
||||
if not world.networkobjects[remotenet].connected.is_set():
|
||||
log.debug('(%s) relay: Blocking link request %s%s -> %s%s because the target '
|
||||
'network is down', irc.name, irc.name, localchan, remotenet, args.channel)
|
||||
irc.error("The target network %s is not connected; refusing to link (you may be "
|
||||
"able to override this with the --force option)." % remotenet)
|
||||
return
|
||||
|
||||
their_ts = world.networkobjects[remotenet].channels[channel].ts
|
||||
if (our_ts < their_ts) and irc.has_cap('has-ts'):
|
||||
log.debug('(%s) relay: Blocking link request %s%s -> %s%s due to bad TS (%s < %s)', irc.name,
|
||||
irc.name, localchan, remotenet, channel, our_ts, their_ts)
|
||||
irc.error("The channel creation date (TS) on %s (%s) is lower than the target "
|
||||
"channel's (%s); refusing to link. You should clear the local channel %s first "
|
||||
"before linking, or use a different local channel (you may be able to "
|
||||
"override this with the --force option)." % (localchan, our_ts, their_ts, localchan))
|
||||
return
|
||||
our_ts = irc.channels[localchan].ts
|
||||
if channel not in world.networkobjects[remotenet].channels:
|
||||
irc.error("Unknown target channel %r." % channel)
|
||||
return
|
||||
|
||||
entry['links'].add((irc.name, localchan))
|
||||
log.info('(%s) relay: Channel %s linked to %s%s by %s.', irc.name,
|
||||
localchan, remotenet, args.channel, irc.get_hostmask(source))
|
||||
initialize_channel(irc, localchan)
|
||||
irc.reply('Done.')
|
||||
their_ts = world.networkobjects[remotenet].channels[channel].ts
|
||||
if (our_ts < their_ts) and irc.has_cap('has-ts'):
|
||||
log.debug('(%s) relay: Blocking link request %s%s -> %s%s due to bad TS (%s < %s)', irc.name,
|
||||
irc.name, localchan, remotenet, channel, our_ts, their_ts)
|
||||
irc.error("The channel creation date (TS) on %s (%s) is lower than the target "
|
||||
"channel's (%s); refusing to link. You should clear the local channel %s first "
|
||||
"before linking, or use a different local channel (you may be able to "
|
||||
"override this with the --force option)." % (localchan, our_ts, their_ts, localchan))
|
||||
return
|
||||
|
||||
entry['links'].add((irc.name, localchan))
|
||||
log.info('(%s) relay: Channel %s linked to %s%s by %s.', irc.name,
|
||||
localchan, remotenet, args.channel, irc.get_hostmask(source))
|
||||
initialize_channel(irc, localchan)
|
||||
irc.reply('Done.')
|
||||
link = utils.add_cmd(link, featured=True)
|
||||
|
||||
def delink(irc, source, args):
|
||||
@ -2861,7 +2841,7 @@ def modedelta(irc, source, args):
|
||||
Mode names are defined using PyLink named modes, and not IRC mode characters: you can find a
|
||||
list of channel named modes and the characters they map to on different IRCds at:
|
||||
|
||||
https://raw.githack.com/jlu5/PyLink/devel/docs/modelists/channel-modes.html
|
||||
https://raw.githack.com/GLolol/PyLink/devel/docs/modelists/channel-modes.html
|
||||
|
||||
Examples of setting modes:
|
||||
|
||||
|
@ -8,7 +8,7 @@ from pylinkirc.log import log
|
||||
try:
|
||||
from cachetools import TTLCache
|
||||
except ImportError:
|
||||
log.warning('servprotect: expiringdict support is deprecated as of PyLink 3.0; consider installing cachetools instead')
|
||||
log.warning('servprotect: expiringdict support is deprecated as of PyLink 2.1; consider installing cachetools instead')
|
||||
from expiringdict import ExpiringDict as TTLCache
|
||||
|
||||
# check for definitions
|
||||
|
@ -115,9 +115,11 @@ def handle_stats(irc, source, command, args):
|
||||
for accountname, accountdata in conf.conf['login'].get('accounts', {}).items():
|
||||
networks = accountdata.get('networks', [])
|
||||
if irc.name in networks or not networks:
|
||||
hosts = ' '.join(accountdata.get('hosts', ['*@*']))
|
||||
needoper = 'needoper' if accountdata.get('require_oper') else ''
|
||||
_num(243, "O %s * %s :%s" % (hosts, accountname, needoper))
|
||||
_num(243, "O %s * %s :%s" %
|
||||
' '.join(accountdata.get('hosts', ['*@*']),
|
||||
accountname,
|
||||
'needoper' if accountdata.get('require_oper') else '')
|
||||
)
|
||||
|
||||
elif stats_type == 'u':
|
||||
# 242/RPL_STATSUPTIME: ":Server Up <days> days <hours>:<minutes>:<seconds>"
|
||||
|
@ -7,17 +7,14 @@ clientbot.py: Clientbot (regular IRC bot) protocol module for PyLink.
|
||||
# works on most networks though!
|
||||
|
||||
import base64
|
||||
import string
|
||||
import threading
|
||||
import time
|
||||
|
||||
from pylinkirc import utils, world
|
||||
from pylinkirc import conf, utils
|
||||
from pylinkirc.classes import *
|
||||
from pylinkirc.log import log
|
||||
from pylinkirc.protocols.ircs2s_common import *
|
||||
|
||||
__all__ = ['ClientbotBaseProtocol', 'ClientbotWrapperProtocol']
|
||||
|
||||
FALLBACK_REALNAME = 'PyLink Relay Mirror Client'
|
||||
|
||||
# IRCv3 capabilities to request when available
|
||||
@ -698,7 +695,7 @@ class ClientbotWrapperProtocol(ClientbotBaseProtocol, IRCCommonProtocol):
|
||||
"""
|
||||
Handles 353 / RPL_NAMREPLY.
|
||||
"""
|
||||
# <- :charybdis.midnight.vpn 353 ice = #test :ice @jlu5
|
||||
# <- :charybdis.midnight.vpn 353 ice = #test :ice @GL
|
||||
|
||||
# Mark "@"-type channels as secret automatically, per RFC2812.
|
||||
channel = args[2]
|
||||
@ -757,9 +754,9 @@ class ClientbotWrapperProtocol(ClientbotBaseProtocol, IRCCommonProtocol):
|
||||
# Send JOIN hook payloads only for users that we know the ident@host of already.
|
||||
# This is mostly used to resync kicked Clientbot users that can't actually be kicked
|
||||
# after a delay.
|
||||
if names and hasattr(self.channels[channel], '_clientbot_initial_who_received'):
|
||||
if names and hasattr(self.irc.channels[channel], '_clientbot_initial_who_received'):
|
||||
log.debug('(%s) handle_353: sending JOIN hook because /WHO was already received for %s',
|
||||
self.name, channel)
|
||||
self.irc.name, channel)
|
||||
return {'channel': channel, 'users': names, 'modes': self._channels[channel].modes,
|
||||
'parse_as': "JOIN"}
|
||||
|
||||
@ -779,10 +776,10 @@ class ClientbotWrapperProtocol(ClientbotBaseProtocol, IRCCommonProtocol):
|
||||
"""
|
||||
# parameter count: 0 1 2 3 4 5 6 7(-1)
|
||||
# <- :charybdis.midnight.vpn 352 ice #test ~pylink 127.0.0.1 charybdis.midnight.vpn ice H+ :0 PyLink
|
||||
# <- :charybdis.midnight.vpn 352 ice #test ~jlu5 127.0.0.1 charybdis.midnight.vpn jlu5 H*@ :0 realname
|
||||
# <- :charybdis.midnight.vpn 352 ice #test ~gl 127.0.0.1 charybdis.midnight.vpn GL H*@ :0 realname
|
||||
# with WHO %cuhsnfar (WHOX) - note, hopcount and realname are separate!
|
||||
# 0 1 2 3 4 5 6 7 8(-1)
|
||||
# <- :charybdis.midnight.vpn 354 ice #test ~jlu5 localhost charybdis.midnight.vpn jlu5 H*@ jlu5 :realname
|
||||
# <- :charybdis.midnight.vpn 354 ice #test ~gl localhost charybdis.midnight.vpn GL H*@ GL :realname
|
||||
channel = args[1]
|
||||
ident = args[2]
|
||||
host = args[3]
|
||||
@ -907,9 +904,9 @@ class ClientbotWrapperProtocol(ClientbotBaseProtocol, IRCCommonProtocol):
|
||||
Handles incoming JOINs, as well as JOIN acknowledgements for us.
|
||||
"""
|
||||
# Classic format:
|
||||
# <- :jlu5|!~jlu5@127.0.0.1 JOIN #whatever
|
||||
# <- :GL|!~GL@127.0.0.1 JOIN #whatever
|
||||
# With extended-join:
|
||||
# <- :jlu5|!~jlu5@127.0.0.1 JOIN #whatever accountname :realname
|
||||
# <- :GL|!~GL@127.0.0.1 JOIN #whatever accountname :realname
|
||||
channel = args[0]
|
||||
self._channels[channel].users.add(source)
|
||||
self.users[source].channels.add(channel)
|
||||
@ -940,7 +937,7 @@ class ClientbotWrapperProtocol(ClientbotBaseProtocol, IRCCommonProtocol):
|
||||
"""
|
||||
Handles incoming KICKs.
|
||||
"""
|
||||
# <- :jlu5!~jlu5@127.0.0.1 KICK #whatever jlu5| :xd
|
||||
# <- :GL!~gl@127.0.0.1 KICK #whatever GL| :xd
|
||||
channel = args[0]
|
||||
target = self._get_UID(args[1], spawn_new=False)
|
||||
|
||||
@ -975,7 +972,7 @@ class ClientbotWrapperProtocol(ClientbotBaseProtocol, IRCCommonProtocol):
|
||||
|
||||
def handle_mode(self, source, command, args):
|
||||
"""Handles MODE changes."""
|
||||
# <- :jlu5!~jlu5@127.0.0.1 MODE #dev +v ice
|
||||
# <- :GL!~gl@127.0.0.1 MODE #dev +v ice
|
||||
# <- :ice MODE ice :+Zi
|
||||
target = args[0]
|
||||
if self.is_channel(target):
|
||||
@ -1000,8 +997,8 @@ class ClientbotWrapperProtocol(ClientbotBaseProtocol, IRCCommonProtocol):
|
||||
def handle_324(self, source, command, args):
|
||||
"""Handles MODE announcements via RPL_CHANNELMODEIS (i.e. the response to /mode #channel)"""
|
||||
# -> MODE #test
|
||||
# <- :midnight.vpn 324 jlu5 #test +nt
|
||||
# <- :midnight.vpn 329 jlu5 #test 1491773459
|
||||
# <- :midnight.vpn 324 GL #test +nt
|
||||
# <- :midnight.vpn 329 GL #test 1491773459
|
||||
channel = args[1]
|
||||
modes = args[2:]
|
||||
log.debug('(%s) Got RPL_CHANNELMODEIS (324) modes %s for %s', self.name, modes, channel)
|
||||
@ -1037,7 +1034,7 @@ class ClientbotWrapperProtocol(ClientbotBaseProtocol, IRCCommonProtocol):
|
||||
|
||||
def handle_nick(self, source, command, args):
|
||||
"""Handles NICK changes."""
|
||||
# <- :jlu5|!~jlu5@127.0.0.1 NICK :jlu5_
|
||||
# <- :GL|!~GL@127.0.0.1 NICK :GL_
|
||||
newnick = args[0]
|
||||
|
||||
if not self.connected.is_set():
|
||||
@ -1060,7 +1057,7 @@ class ClientbotWrapperProtocol(ClientbotBaseProtocol, IRCCommonProtocol):
|
||||
"""
|
||||
Handles incoming PARTs.
|
||||
"""
|
||||
# <- :jlu5|!~jlu5@127.0.0.1 PART #whatever
|
||||
# <- :GL|!~GL@127.0.0.1 PART #whatever
|
||||
channels = args[0].split(',')
|
||||
try:
|
||||
reason = args[1]
|
||||
@ -1157,7 +1154,7 @@ class ClientbotWrapperProtocol(ClientbotBaseProtocol, IRCCommonProtocol):
|
||||
channel = args[1]
|
||||
target = args[2]
|
||||
if channel not in self.channels:
|
||||
log.warning('(%s) got ban mode +%s %s on unknown channel %s?', self.name, banmode, target, channel)
|
||||
log.warning('(%s) got ban mode +%s %s on unknown channel %s?', self.name, banmode, target)
|
||||
else:
|
||||
# Just apply the mode; we send out a mode hook only when the corresponding ban list has finished sending.
|
||||
self.apply_modes(channel, [('+%s' % banmode, target)])
|
||||
|
@ -4,12 +4,10 @@ hybrid.py: IRCD-Hybrid protocol module for PyLink.
|
||||
|
||||
import time
|
||||
|
||||
from pylinkirc import conf
|
||||
from pylinkirc import conf, utils
|
||||
from pylinkirc.classes import *
|
||||
from pylinkirc.log import log
|
||||
from pylinkirc.protocols.ts6 import TS6Protocol
|
||||
|
||||
__all__ = ['HybridProtocol']
|
||||
from pylinkirc.protocols.ts6 import *
|
||||
|
||||
|
||||
# This protocol module inherits from the TS6 protocol.
|
||||
@ -131,8 +129,8 @@ class HybridProtocol(TS6Protocol):
|
||||
"""Updates the ident, host, or realname of a PyLink client."""
|
||||
# https://github.com/ircd-hybrid/ircd-hybrid/blob/58323b8/modules/m_svsmode.c#L40-L103
|
||||
# parv[0] = command
|
||||
# parv[1] = nickname <-- UID works too -jlu5
|
||||
# parv[2] = TS <-- Of the user, not the current time. -jlu5
|
||||
# parv[1] = nickname <-- UID works too -GLolol
|
||||
# parv[2] = TS <-- Of the user, not the current time. -GLolol
|
||||
# parv[3] = mode
|
||||
# parv[4] = optional argument (services account, vhost)
|
||||
field = field.upper()
|
||||
@ -142,17 +140,11 @@ class HybridProtocol(TS6Protocol):
|
||||
if field == 'HOST':
|
||||
self.users[target].host = text
|
||||
# On Hybrid, it appears that host changing is actually just forcing umode
|
||||
# "+x <hostname>" on the target. -jlu5
|
||||
# "+x <hostname>" on the target. -GLolol
|
||||
self._send_with_prefix(self.sid, 'SVSMODE %s %s +x %s' % (target, ts, text))
|
||||
else:
|
||||
raise NotImplementedError("Changing field %r of a client is unsupported by this protocol." % field)
|
||||
|
||||
def oper_notice(self, source, text):
|
||||
"""
|
||||
Send a message to all opers.
|
||||
"""
|
||||
self._send_with_prefix(source, 'GLOBOPS :%s' % text)
|
||||
|
||||
def set_server_ban(self, source, duration, user='*', host='*', reason='User banned'):
|
||||
"""
|
||||
Sets a server ban.
|
||||
@ -217,14 +209,11 @@ class HybridProtocol(TS6Protocol):
|
||||
# Call the OPERED UP hook if +o is being added to the mode list.
|
||||
self._check_oper_status_change(uid, parsedmodes)
|
||||
|
||||
# Track SSL/TLS status
|
||||
has_ssl = self.users[uid].ssl = ('+S', None) in parsedmodes
|
||||
|
||||
# Set the account name if present
|
||||
if account:
|
||||
self.call_hooks([uid, 'CLIENT_SERVICES_LOGIN', {'text': account}])
|
||||
|
||||
return {'uid': uid, 'ts': ts, 'nick': nick, 'realname': realname, 'host': host, 'ident': ident, 'ip': ip, 'secure': has_ssl}
|
||||
return {'uid': uid, 'ts': ts, 'nick': nick, 'realname': realname, 'host': host, 'ident': ident, 'ip': ip}
|
||||
|
||||
def handle_tburst(self, numeric, command, args):
|
||||
"""Handles incoming topic burst (TBURST) commands."""
|
||||
@ -264,7 +253,7 @@ class HybridProtocol(TS6Protocol):
|
||||
# Login sequence (tested with Anope 2.0.4-git):
|
||||
# A mode change +d accountname is used to propagate logins,
|
||||
# before setting umode +r on the target.
|
||||
# <- :5ANAAAAAG SVSMODE 5HYAAAAAA 1460175209 +d jlu5
|
||||
# <- :5ANAAAAAG SVSMODE 5HYAAAAAA 1460175209 +d GL
|
||||
# <- :5ANAAAAAG SVSMODE 5HYAAAAAA 1460175209 +r
|
||||
|
||||
# Logout sequence:
|
||||
|
@ -5,19 +5,17 @@ inspircd.py: InspIRCd 2.0, 3.x protocol module for PyLink.
|
||||
import threading
|
||||
import time
|
||||
|
||||
from pylinkirc import conf
|
||||
from pylinkirc import conf, utils
|
||||
from pylinkirc.classes import *
|
||||
from pylinkirc.log import log
|
||||
from pylinkirc.protocols.ts6_common import TS6BaseProtocol
|
||||
|
||||
__all__ = ['InspIRCdProtocol']
|
||||
from pylinkirc.protocols.ts6_common import *
|
||||
|
||||
|
||||
class InspIRCdProtocol(TS6BaseProtocol):
|
||||
|
||||
S2S_BUFSIZE = 0 # InspIRCd allows infinitely long S2S messages, so set bufsize to infinite
|
||||
SUPPORTED_IRCDS = ['insp20', 'insp3']
|
||||
DEFAULT_IRCD = SUPPORTED_IRCDS[1]
|
||||
DEFAULT_IRCD = SUPPORTED_IRCDS[0]
|
||||
|
||||
MAX_PROTO_VER = 1205 # anything above this warns (not officially supported)
|
||||
|
||||
@ -26,7 +24,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
||||
|
||||
self.protocol_caps |= {'slash-in-nicks', 'slash-in-hosts', 'underscore-in-hosts'}
|
||||
|
||||
# This is only the default value - on InspIRCd 3 it will be negotiated on connect in CAPAB CAPABILITIES
|
||||
# Set our case mapping (rfc1459 maps "\" and "|" together, for example).
|
||||
self.casemapping = 'rfc1459'
|
||||
|
||||
# Raw commands sent from servers vary from protocol to protocol. Here, we map
|
||||
@ -334,12 +332,6 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
||||
|
||||
self.call_hooks([self.sid, 'CHGNAME',
|
||||
{'target': target, 'newgecos': text}])
|
||||
def oper_notice(self, source, text):
|
||||
"""
|
||||
Send a message to all opers.
|
||||
"""
|
||||
# <- :70M SNONOTICE G :From jlu5: aaaaaa
|
||||
self._send_with_prefix(self.sid, 'SNONOTICE G :From %s: %s' % (self.get_friendly_name(source), text))
|
||||
|
||||
def numeric(self, source, numeric, target, text):
|
||||
"""Sends raw numerics from a server to a remote client."""
|
||||
@ -499,8 +491,8 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
||||
Handles the CAPAB command, used for capability negotiation with our
|
||||
uplink.
|
||||
"""
|
||||
# 5 CAPAB subcommands are usually sent on connect (excluding START and END):
|
||||
# CAPAB MODULES, MODSUPPORT, CHANMODES, USERMODES, and CAPABILITIES
|
||||
# 6 CAPAB commands are usually sent on connect: CAPAB START, MODULES,
|
||||
# MODSUPPORT, CHANMODES, USERMODES, and CAPABILITIES.
|
||||
# We check just about everything except MODULES
|
||||
|
||||
if args[0] == 'START':
|
||||
@ -511,7 +503,6 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
||||
# <- CAPAB START 1205
|
||||
self.remote_proto_ver = protocol_version = int(args[1])
|
||||
|
||||
log.debug("(%s) handle_capab: got remote protocol version %s", self.name, protocol_version)
|
||||
if protocol_version < self.proto_ver:
|
||||
raise ProtocolError("Remote protocol version is too old! "
|
||||
"At least %s is needed. (got %s)" %
|
||||
@ -521,11 +512,11 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
||||
"and should not be relied upon for anything important.",
|
||||
self.name)
|
||||
elif protocol_version >= 1205 > self.proto_ver:
|
||||
log.warning("(%s) PyLink 3.0 introduces native support for InspIRCd 3. "
|
||||
"You should enable this by setting the 'target_version' option in your "
|
||||
"InspIRCd server block to 'insp3'. Otherwise, some features will not "
|
||||
"work correctly!", self.name)
|
||||
log.warning("(%s) Falling back to InspIRCd 2.0 (compatibility) mode.", self.name)
|
||||
log.info("(%s) PyLink 2.1 introduces native support for InspIRCd 3.0. "
|
||||
"You can enable this by setting the 'target_version' option in your "
|
||||
"InspIRCd server block to 'insp3'.", self.name)
|
||||
log.info("(%s) Falling back to InspIRCd 2.0 (compatibility) mode.", self.name)
|
||||
log.debug("(%s) inspircd: got remote protocol version %s", self.name, protocol_version)
|
||||
|
||||
if self.proto_ver >= 1205:
|
||||
# Clear mode lists, they will be negotiated during burst
|
||||
@ -604,9 +595,6 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
||||
if name == 'reginvite': # Reginvite? That's an odd name.
|
||||
name = 'regonly'
|
||||
|
||||
if name == 'antiredirect': # User mode +L
|
||||
name = 'noforward'
|
||||
|
||||
if name == 'founder': # Channel mode +q
|
||||
# Founder, owner; same thing. m_customprefix allows you to name it anything you like,
|
||||
# but PyLink uses the latter in its definitions
|
||||
@ -634,17 +622,13 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
||||
|
||||
# First, turn the arguments into a dict
|
||||
caps = self.parse_isupport(args[-1])
|
||||
log.debug("(%s) handle_capab: capabilities list is %s", self.name, caps)
|
||||
log.debug("(%s) capabilities list: %s", self.name, caps)
|
||||
|
||||
# Store the max nick and channel lengths
|
||||
if 'NICKMAX' in caps:
|
||||
self.maxnicklen = int(caps['NICKMAX'])
|
||||
if 'CHANMAX' in caps:
|
||||
self.maxchanlen = int(caps['CHANMAX'])
|
||||
# Casemapping - this is only sent in InspIRCd 3.x
|
||||
if 'CASEMAPPING' in caps:
|
||||
self.casemapping = caps['CASEMAPPING']
|
||||
log.debug('(%s) handle_capab: updated casemapping to %s', self.name, self.casemapping)
|
||||
|
||||
# InspIRCd 2 only: mode & prefix definitions are sent as CAPAB CAPABILITIES CHANMODES/USERMODES/PREFIX
|
||||
if self.proto_ver < 1205:
|
||||
@ -658,7 +642,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
||||
# Separate the prefixes field (e.g. "(Yqaohv)!~&@%+") into a
|
||||
# dict mapping mode characters to mode prefixes.
|
||||
self.prefixmodes = self.parse_isupport_prefixes(caps['PREFIX'])
|
||||
log.debug('(%s) handle_capab: self.prefixmodes set to %r', self.name,
|
||||
log.debug('(%s) self.prefixmodes set to %r', self.name,
|
||||
self.prefixmodes)
|
||||
|
||||
elif args[0] == 'MODSUPPORT':
|
||||
@ -768,7 +752,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
||||
|
||||
def handle_uid(self, numeric, command, args):
|
||||
"""Handles incoming UID commands (user introduction)."""
|
||||
# :70M UID 70MAAAAAB 1429934638 jlu5 0::1 hidden-7j810p.9mdf.lrek.0000.0000.IP jlu5 0::1 1429934638 +Wioswx +ACGKNOQXacfgklnoqvx :realname
|
||||
# :70M UID 70MAAAAAB 1429934638 GL 0::1 hidden-7j810p.9mdf.lrek.0000.0000.IP gl 0::1 1429934638 +Wioswx +ACGKNOQXacfgklnoqvx :realname
|
||||
uid, ts, nick, realhost, host, ident, ip = args[0:7]
|
||||
|
||||
ts = int(ts)
|
||||
@ -783,8 +767,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
||||
self._check_oper_status_change(uid, parsedmodes)
|
||||
|
||||
self.servers[numeric].users.add(uid)
|
||||
# InspIRCd sends SSL status in the metadata command, so the info is not known at this point
|
||||
return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip, 'secure': None}
|
||||
return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip}
|
||||
|
||||
def handle_server(self, source, command, args):
|
||||
"""Handles incoming SERVER commands (introduction of servers)."""
|
||||
@ -853,10 +836,10 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
||||
def handle_ftopic(self, source, command, args):
|
||||
"""Handles incoming topic changes."""
|
||||
# insp2 (only used for server senders):
|
||||
# <- :70M FTOPIC #channel 1434510754 jlu5!jlu5@escape.the.dreamland.ca :Some channel topic
|
||||
# <- :70M FTOPIC #channel 1434510754 GLo|o|!GLolol@escape.the.dreamland.ca :Some channel topic
|
||||
|
||||
# insp3 (used for server AND user senders):
|
||||
# <- :3IN FTOPIC #qwerty 1556828864 1556844505 jlu5!jlu5@midnight-umk.of4.0.127.IP :1234abcd
|
||||
# <- :3IN FTOPIC #qwerty 1556828864 1556844505 GL!gl@midnight-umk.of4.0.127.IP :1234abcd
|
||||
# <- :3INAAAAAA FTOPIC #qwerty 1556828864 1556844248 :topic text
|
||||
# chan creation time ^ ^ topic set time (the one we want)
|
||||
|
||||
@ -993,8 +976,6 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
||||
self._modsupport.add(module[1:])
|
||||
else:
|
||||
log.warning('(%s) Got unknown METADATA modules string: %r', self.name, args[-1])
|
||||
elif args[1] == 'ssl_cert' and uid in self.users:
|
||||
self.users[uid].ssl = True
|
||||
|
||||
def handle_version(self, numeric, command, args):
|
||||
"""
|
||||
|
@ -4,39 +4,53 @@ ircs2s_common.py: Common base protocol class with functions shared by TS6 and P1
|
||||
|
||||
import re
|
||||
import time
|
||||
from collections import defaultdict
|
||||
|
||||
from pylinkirc import conf
|
||||
from pylinkirc import conf, utils
|
||||
from pylinkirc.classes import IRCNetwork, ProtocolError
|
||||
from pylinkirc.log import log
|
||||
|
||||
__all__ = ['UIDGenerator', 'IRCCommonProtocol', 'IRCS2SProtocol']
|
||||
|
||||
class UIDGenerator():
|
||||
class IncrementalUIDGenerator():
|
||||
"""
|
||||
Generate UIDs for IRC S2S.
|
||||
Incremental UID Generator module, adapted from InspIRCd source:
|
||||
https://github.com/inspircd/inspircd/blob/f449c6b296ab/src/server.cpp#L85-L156
|
||||
"""
|
||||
|
||||
def __init__(self, uidchars, length, sid):
|
||||
self.uidchars = uidchars # corpus of characters to choose from
|
||||
self.length = length # desired length of uid part, padded with uidchars[0]
|
||||
self.sid = str(sid) # server id (prefixed to every result)
|
||||
self.counter = 0
|
||||
def __init__(self, sid):
|
||||
if not (hasattr(self, 'allowedchars') and hasattr(self, 'length')):
|
||||
raise RuntimeError("Allowed characters list not defined. Subclass "
|
||||
"%s by defining self.allowedchars and self.length "
|
||||
"and then calling super().__init__()." % self.__class__.__name__)
|
||||
self.uidchars = [self.allowedchars[0]]*self.length
|
||||
self.sid = str(sid)
|
||||
|
||||
def increment(self, pos=None):
|
||||
"""
|
||||
Increments the UID generator to the next available UID.
|
||||
"""
|
||||
# Position starts at 1 less than the UID length.
|
||||
if pos is None:
|
||||
pos = self.length - 1
|
||||
|
||||
# If we're at the last character in the list of allowed ones, reset
|
||||
# and increment the next level above.
|
||||
if self.uidchars[pos] == self.allowedchars[-1]:
|
||||
self.uidchars[pos] = self.allowedchars[0]
|
||||
self.increment(pos-1)
|
||||
else:
|
||||
# Find what position in the allowed characters list we're currently
|
||||
# on, and add one.
|
||||
idx = self.allowedchars.find(self.uidchars[pos])
|
||||
self.uidchars[pos] = self.allowedchars[idx+1]
|
||||
|
||||
def next_uid(self):
|
||||
"""
|
||||
Returns the next unused UID for the server.
|
||||
"""
|
||||
uid = ''
|
||||
num = self.counter
|
||||
if num >= (len(self.uidchars) ** self.length):
|
||||
raise RuntimeError("UID overflowed")
|
||||
while num > 0:
|
||||
num, index = divmod(num, len(self.uidchars))
|
||||
uid = self.uidchars[index] + uid
|
||||
|
||||
self.counter += 1
|
||||
uid = uid.rjust(self.length, self.uidchars[0])
|
||||
return self.sid + uid
|
||||
uid = self.sid + ''.join(self.uidchars)
|
||||
self.increment()
|
||||
return uid
|
||||
|
||||
class IRCCommonProtocol(IRCNetwork):
|
||||
|
||||
@ -82,7 +96,7 @@ class IRCCommonProtocol(IRCNetwork):
|
||||
joined_arg = ' '.join(args[idx:])[1:] # Cut off the leading : as well
|
||||
real_args.append(joined_arg)
|
||||
break
|
||||
elif arg.strip(' '): # Skip empty args that aren't part of the multi-word arg
|
||||
elif arg.strip(): # Skip empty args that aren't part of the multi-word arg
|
||||
real_args.append(arg)
|
||||
|
||||
return real_args
|
||||
@ -295,18 +309,19 @@ class IRCS2SProtocol(IRCCommonProtocol):
|
||||
args = self.parse_args(data)
|
||||
|
||||
sender = args[0]
|
||||
if sender.startswith(':'):
|
||||
sender = sender[1:]
|
||||
sender = sender.lstrip(':')
|
||||
|
||||
# If the sender isn't in numeric format, try to convert it automatically.
|
||||
sender_sid = self._get_SID(sender)
|
||||
sender_uid = self._get_UID(sender)
|
||||
# If the sender isn't in numeric format, try to convert it automatically.
|
||||
sender_sid = self._get_SID(sender)
|
||||
sender_uid = self._get_UID(sender)
|
||||
|
||||
if sender_sid in self.servers:
|
||||
sender = sender_sid
|
||||
elif sender_uid in self.users:
|
||||
sender = sender_uid
|
||||
else:
|
||||
if sender_sid in self.servers:
|
||||
# Sender is a server (converting from name to SID gave a valid result).
|
||||
sender = sender_sid
|
||||
elif sender_uid in self.users:
|
||||
# Sender is a user (converting from name to UID gave a valid result).
|
||||
sender = sender_uid
|
||||
elif not (args[0].startswith(':')):
|
||||
# No sender prefix; treat as coming from uplink IRCd.
|
||||
sender = self.uplink
|
||||
args.insert(0, sender)
|
||||
@ -327,7 +342,7 @@ class IRCS2SProtocol(IRCCommonProtocol):
|
||||
|
||||
if command == 'ENCAP':
|
||||
# Special case for TS6 encapsulated commands (ENCAP), in forms like this:
|
||||
# <- :00A ENCAP * SU 42XAAAAAC :jlu5
|
||||
# <- :00A ENCAP * SU 42XAAAAAC :GLolol
|
||||
command = args[1]
|
||||
args = args[2:]
|
||||
log.debug("(%s) Rewriting incoming ENCAP to command %s (args: %s)", self.name, command, args)
|
||||
@ -370,12 +385,6 @@ class IRCS2SProtocol(IRCCommonProtocol):
|
||||
# handle_part() does that just fine.
|
||||
self.handle_part(target, 'KICK', [channel])
|
||||
|
||||
def oper_notice(self, source, text):
|
||||
"""
|
||||
Send a message to all opers.
|
||||
"""
|
||||
self._send_with_prefix(source, 'WALLOPS :%s' % text)
|
||||
|
||||
def numeric(self, source, numeric, target, text):
|
||||
"""Sends raw numerics from a server to a remote client. This is used for WHOIS replies."""
|
||||
# Mangle the target for IRCds that require it.
|
||||
@ -497,7 +506,7 @@ class IRCS2SProtocol(IRCCommonProtocol):
|
||||
return
|
||||
|
||||
# TS6-style kills look something like this:
|
||||
# <- :jlu5 KILL 38QAAAAAA :hidden-1C620195!jlu5 (test)
|
||||
# <- :GL KILL 38QAAAAAA :hidden-1C620195!GL (test)
|
||||
# What we actually want is to format a pretty kill message, in the form
|
||||
# "Killed (killername (reason))".
|
||||
|
||||
@ -513,16 +522,16 @@ class IRCS2SProtocol(IRCCommonProtocol):
|
||||
# Get the reason, which is enclosed in brackets.
|
||||
killmsg = ' '.join(args[1].split(" ")[1:])[1:-1]
|
||||
if not killmsg:
|
||||
log.warning('(%s) Failed to extract kill reason: %r', self.name, args)
|
||||
killmsg = args[1]
|
||||
log.warning('(%s) Failed to extract kill reason: %r', irc.name, args)
|
||||
killmsg = '<No reason given>'
|
||||
else:
|
||||
# We already have a preformatted kill, so just pass it on as is.
|
||||
# XXX: this does create a convoluted kill string if we want to forward kills
|
||||
# over relay.
|
||||
# InspIRCd:
|
||||
# <- :1MLAAAAA1 KILL 0ALAAAAAC :Killed (jlu5 (test))
|
||||
# <- :1MLAAAAA1 KILL 0ALAAAAAC :Killed (GL (test))
|
||||
# ngIRCd:
|
||||
# <- :jlu5 KILL PyLink-devel :KILLed by jlu5: ?
|
||||
# <- :GL KILL PyLink-devel :KILLed by GL: ?
|
||||
killmsg = args[1]
|
||||
|
||||
return {'target': killed, 'text': killmsg, 'userdata': userdata}
|
||||
@ -564,7 +573,7 @@ class IRCS2SProtocol(IRCCommonProtocol):
|
||||
# <- :70MAAAAAA MODE 70MAAAAAA -i+xc
|
||||
|
||||
# P10:
|
||||
# <- ABAAA M jlu5 -w
|
||||
# <- ABAAA M GL -w
|
||||
# <- ABAAA M #test +v ABAAB 1460747615
|
||||
# <- ABAAA OM #test +h ABAAA
|
||||
target = self._get_UID(args[0])
|
||||
@ -674,14 +683,10 @@ class IRCS2SProtocol(IRCCommonProtocol):
|
||||
# TS6:
|
||||
# <- :1SRAAGB4T QUIT :Quit: quit message goes here
|
||||
# P10:
|
||||
# <- ABAAB Q :Killed (jlu5_ (bangbang))
|
||||
# <- ABAAB Q :Killed (GL_ (bangbang))
|
||||
userdata = self._remove_client(numeric)
|
||||
if userdata:
|
||||
try:
|
||||
reason = args[0]
|
||||
except IndexError:
|
||||
reason = ''
|
||||
return {'text': reason, 'userdata': userdata}
|
||||
return {'text': args[0], 'userdata': userdata}
|
||||
|
||||
def handle_stats(self, numeric, command, args):
|
||||
"""Handles the IRC STATS command."""
|
||||
|
@ -3,9 +3,7 @@ nefarious.py: Migration stub to the new P10 protocol module.
|
||||
"""
|
||||
|
||||
from pylinkirc.log import log
|
||||
from pylinkirc.protocols.p10 import P10Protocol
|
||||
|
||||
__all__ = ['NefariousProtocol']
|
||||
from pylinkirc.protocols.p10 import *
|
||||
|
||||
|
||||
class NefariousProtocol(P10Protocol):
|
||||
|
@ -15,8 +15,6 @@ from pylinkirc.classes import *
|
||||
from pylinkirc.log import log
|
||||
from pylinkirc.protocols.ircs2s_common import *
|
||||
|
||||
__all__ = ['NgIRCdProtocol']
|
||||
|
||||
|
||||
class NgIRCdProtocol(IRCS2SProtocol):
|
||||
def __init__(self, irc):
|
||||
@ -61,31 +59,15 @@ class NgIRCdProtocol(IRCS2SProtocol):
|
||||
self._caps.clear()
|
||||
|
||||
self.cmodes.update({
|
||||
'banexception': 'e',
|
||||
'invex': 'I',
|
||||
'noinvite': 'V',
|
||||
'nokick': 'Q',
|
||||
'nonick': 'N',
|
||||
'operonly': 'O',
|
||||
'permanent': 'P',
|
||||
'registered': 'r',
|
||||
'regmoderated': 'M',
|
||||
'regonly': 'R',
|
||||
'sslonly': 'z'
|
||||
'banexception': 'e', 'invex': 'I', 'regmoderated': 'M', 'nonick': 'N',
|
||||
'operonly': 'O', 'permanent': 'P', 'nokick': 'Q', 'registered': 'r',
|
||||
'regonly': 'R', 'noinvite': 'V', 'sslonly': 'z'
|
||||
})
|
||||
|
||||
self.umodes.update({
|
||||
'away': 'a',
|
||||
'bot': 'B',
|
||||
'cloak': 'x',
|
||||
'deaf_commonchan': 'C',
|
||||
'floodexempt': 'F',
|
||||
'hidechans': 'I',
|
||||
'privdeaf': 'b',
|
||||
'registered': 'R',
|
||||
'restricted': 'r',
|
||||
'servprotect': 'q',
|
||||
'sno_clientconnections': 'c'
|
||||
'away': 'a', 'deaf': 'b', 'bot': 'B', 'sno_clientconnections': 'c',
|
||||
'deaf_commonchan': 'C', 'floodexempt': 'f', 'hidechans': 'I',
|
||||
'servprotect': 'q', 'restricted': 'r', 'registered': 'R', 'cloak': 'x'
|
||||
})
|
||||
|
||||
def spawn_client(self, nick, ident='null', host='null', realhost=None, modes=set(),
|
||||
@ -117,7 +99,7 @@ class NgIRCdProtocol(IRCS2SProtocol):
|
||||
|
||||
# Grab our server token; this is used instead of server name to denote where the client is.
|
||||
server_token = server.rsplit('@')[-1]
|
||||
# <- :ngircd.midnight.local NICK jlu5 1 ~jlu5 localhost 1 +io :realname
|
||||
# <- :ngircd.midnight.local NICK GL 1 ~gl localhost 1 +io :realname
|
||||
self._send_with_prefix(server, 'NICK %s %s %s %s %s %s :%s' % (nick, self.servers[server].hopcount,
|
||||
ident, host, server_token, self.join_modes(modes), realname))
|
||||
return userobj
|
||||
@ -285,7 +267,7 @@ class NgIRCdProtocol(IRCS2SProtocol):
|
||||
"""
|
||||
Sets a server ban.
|
||||
"""
|
||||
# <- :jlu5 GLINE *!*@bad.user 3d :test
|
||||
# <- :GL GLINE *!*@bad.user 3d :test
|
||||
assert not (user == host == '*'), "Refusing to set ridiculous ban on *@*"
|
||||
self._send_with_prefix(source, 'GLINE *!%s@%s %s :%s' % (user, host, duration, reason))
|
||||
|
||||
@ -380,8 +362,8 @@ class NgIRCdProtocol(IRCS2SProtocol):
|
||||
def handle_join(self, source, command, args):
|
||||
# RFC 2813 is odd to say the least... https://tools.ietf.org/html/rfc2813#section-4.2.1
|
||||
# Basically, we expect messages of the forms:
|
||||
# <- :jlu5 JOIN #test\x07o
|
||||
# <- :jlu5 JOIN #moretest
|
||||
# <- :GL JOIN #test\x07o
|
||||
# <- :GL JOIN #moretest
|
||||
for chanpair in args[0].split(','):
|
||||
# Normalize channel case.
|
||||
try:
|
||||
@ -426,7 +408,7 @@ class NgIRCdProtocol(IRCS2SProtocol):
|
||||
|
||||
def handle_metadata(self, source, command, args):
|
||||
"""Handles various user metadata for ngIRCd (cloaked host, account name, etc.)"""
|
||||
# <- :ngircd.midnight.local METADATA jlu5 cloakhost :hidden-3a2a739e.ngircd.midnight.local
|
||||
# <- :ngircd.midnight.local METADATA GL cloakhost :hidden-3a2a739e.ngircd.midnight.local
|
||||
target = self._get_UID(args[0])
|
||||
|
||||
if target not in self.users:
|
||||
@ -461,7 +443,7 @@ class NgIRCdProtocol(IRCS2SProtocol):
|
||||
"""
|
||||
if len(args) >= 2:
|
||||
# User introduction:
|
||||
# <- :ngircd.midnight.local NICK jlu5 1 ~jlu5 localhost 1 +io :realname
|
||||
# <- :ngircd.midnight.local NICK GL 1 ~gl localhost 1 +io :realname
|
||||
nick = args[0]
|
||||
assert source in self.servers, "Server %r tried to introduce nick %r but isn't in the servers index?" % (source, nick)
|
||||
self._check_nick_collision(nick)
|
||||
@ -488,13 +470,13 @@ class NgIRCdProtocol(IRCS2SProtocol):
|
||||
'parse_as': 'UID', 'ip': '0.0.0.0'}
|
||||
else:
|
||||
# Nick changes:
|
||||
# <- :jlu5 NICK :jlu5_
|
||||
# <- :GL NICK :GL_
|
||||
oldnick = self.users[source].nick
|
||||
newnick = self.users[source].nick = args[0]
|
||||
return {'newnick': newnick, 'oldnick': oldnick}
|
||||
|
||||
def handle_njoin(self, source, command, args):
|
||||
# <- :ngircd.midnight.local NJOIN #test :tester,@%jlu5
|
||||
# <- :ngircd.midnight.local NJOIN #test :tester,@%GL
|
||||
|
||||
channel = args[0]
|
||||
chandata = self._channels[channel].deepcopy()
|
||||
|
@ -3,8 +3,6 @@ p10.py: P10 protocol module for PyLink, supporting Nefarious IRCu and others.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import socket
|
||||
import string
|
||||
import struct
|
||||
import time
|
||||
from ipaddress import ip_address
|
||||
@ -14,16 +12,14 @@ from pylinkirc.classes import *
|
||||
from pylinkirc.log import log
|
||||
from pylinkirc.protocols.ircs2s_common import *
|
||||
|
||||
__all__ = ['P10Protocol']
|
||||
|
||||
class P10UIDGenerator(IncrementalUIDGenerator):
|
||||
"""Implements an incremental P10 UID Generator."""
|
||||
|
||||
class P10UIDGenerator(UIDGenerator):
|
||||
"""Implements a P10 UID Generator."""
|
||||
|
||||
def __init__(self, sid):
|
||||
uidchars = string.ascii_uppercase + string.ascii_lowercase + string.digits + '[]'
|
||||
length = 3
|
||||
super().__init__(uidchars, length, sid)
|
||||
def __init__(self, sid):
|
||||
self.allowedchars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789[]'
|
||||
self.length = 3
|
||||
super().__init__(sid)
|
||||
|
||||
def p10b64encode(num, length=2):
|
||||
"""
|
||||
@ -199,7 +195,7 @@ class P10Protocol(IRCS2SProtocol):
|
||||
@staticmethod
|
||||
def decode_p10_ip(ip):
|
||||
"""Decodes a P10 IP."""
|
||||
# Many thanks to Jobe @ evilnet for the code on what to do here. :) -jlu5
|
||||
# Many thanks to Jobe @ evilnet for the code on what to do here. :) -GL
|
||||
|
||||
if len(ip) == 6: # IPv4
|
||||
# Pad the characters with two \x00's (represented in P10 B64 as AA)
|
||||
@ -407,7 +403,7 @@ class P10Protocol(IRCS2SProtocol):
|
||||
|
||||
def kill(self, numeric, target, reason):
|
||||
"""Sends a kill from a PyLink client/server."""
|
||||
# <- ABAAA D AyAAA :nefarious.midnight.vpn!jlu5 (test)
|
||||
# <- ABAAA D AyAAA :nefarious.midnight.vpn!GL (test)
|
||||
|
||||
if (not self.is_internal_client(numeric)) and \
|
||||
(not self.is_internal_server(numeric)):
|
||||
@ -464,7 +460,7 @@ class P10Protocol(IRCS2SProtocol):
|
||||
|
||||
def mode(self, numeric, target, modes, ts=None):
|
||||
"""Sends mode changes from a PyLink client/server."""
|
||||
# <- ABAAA M jlu5 -w
|
||||
# <- ABAAA M GL -w
|
||||
# <- ABAAA M #test +v ABAAB 1460747615
|
||||
|
||||
if (not self.is_internal_client(numeric)) and \
|
||||
@ -495,7 +491,7 @@ class P10Protocol(IRCS2SProtocol):
|
||||
real_target = target
|
||||
else:
|
||||
assert target in self.users, "Unknown mode target %s" % target
|
||||
# P10 uses nicks in user MODE targets, NOT UIDs. ~jlu5
|
||||
# P10 uses nicks in user MODE targets, NOT UIDs. ~GL
|
||||
real_target = self.users[target].nick
|
||||
|
||||
self.apply_modes(target, modes)
|
||||
@ -511,7 +507,7 @@ class P10Protocol(IRCS2SProtocol):
|
||||
|
||||
def nick(self, numeric, newnick):
|
||||
"""Changes the nick of a PyLink client."""
|
||||
# <- ABAAA N jlu5_ 1460753763
|
||||
# <- ABAAA N GL_ 1460753763
|
||||
if not self.is_internal_client(numeric):
|
||||
raise LookupError('No such PyLink client exists.')
|
||||
|
||||
@ -524,15 +520,9 @@ class P10Protocol(IRCS2SProtocol):
|
||||
def numeric(self, source, numeric, target, text):
|
||||
"""Sends raw numerics from a server to a remote client. This is used for WHOIS
|
||||
replies."""
|
||||
# <- AB 311 AyAAA jlu5 ~jlu5 nefarious.midnight.vpn * :realname
|
||||
# <- AB 311 AyAAA GL ~gl nefarious.midnight.vpn * :realname
|
||||
self._send_with_prefix(source, '%s %s %s' % (numeric, target, text))
|
||||
|
||||
def oper_notice(self, source, text):
|
||||
"""
|
||||
Send a message to all opers.
|
||||
"""
|
||||
self._send_with_prefix(source, 'WA :%s' % text)
|
||||
|
||||
def part(self, client, channel, reason=None):
|
||||
"""Sends a part from a PyLink client."""
|
||||
|
||||
@ -565,7 +555,7 @@ class P10Protocol(IRCS2SProtocol):
|
||||
assert not (user == host == '*'), "Refusing to set ridiculous ban on *@*"
|
||||
|
||||
# https://github.com/evilnet/nefarious2/blob/master/doc/p10.txt#L535
|
||||
# <- ABAAA jlu5 * +test@test.host 30 1500300185 1500300215 :haha, you're banned now!!!!1
|
||||
# <- ABAAA GL * +test@test.host 30 1500300185 1500300215 :haha, you're banned now!!!!1
|
||||
currtime = int(time.time())
|
||||
|
||||
if duration == 0 or duration > GLINE_MAX_EXPIRE:
|
||||
@ -754,7 +744,7 @@ class P10Protocol(IRCS2SProtocol):
|
||||
|
||||
def topic(self, source, target, text):
|
||||
"""Sends a TOPIC change from a PyLink client or server."""
|
||||
# <- ABAAA T #test jlu5!~jlu5@nefarious.midnight.vpn 1460852591 1460855795 :blah
|
||||
# <- ABAAA T #test GL!~gl@nefarious.midnight.vpn 1460852591 1460855795 :blah
|
||||
# First timestamp is channel creation time, second is current time,
|
||||
if (not self.is_internal_client(source)) and (not self.is_internal_server(source)):
|
||||
raise LookupError('No such PyLink client/server exists.')
|
||||
@ -822,16 +812,6 @@ class P10Protocol(IRCS2SProtocol):
|
||||
|
||||
### HANDLERS
|
||||
|
||||
def handle_events(self, data):
|
||||
"""
|
||||
Events handler for the P10 protocol. This is mostly the same as RFC1459, with extra handling
|
||||
for the fact that P10 does not send source numerics prefixed with a ":".
|
||||
"""
|
||||
# After the initial PASS & SERVER message, every following message should be prefixed
|
||||
if self.uplink and not data.startswith(":"):
|
||||
data = ':' + data
|
||||
return super().handle_events(data)
|
||||
|
||||
def post_connect(self):
|
||||
"""Initializes a connection to a server."""
|
||||
ts = self.start_ts
|
||||
@ -846,7 +826,7 @@ class P10Protocol(IRCS2SProtocol):
|
||||
# 4 <link TS>
|
||||
# 5 <protocol>
|
||||
# 6 <numeric of new server><max client numeric>
|
||||
# 7 <flags> <-- Mark ourselves as a service with IPv6 support (+s & +6) -jlu5
|
||||
# 7 <flags> <-- Mark ourselves as a service with IPv6 support (+s & +6) -GLolol
|
||||
# -1 <description of new server>
|
||||
|
||||
name = self.serverdata["hostname"]
|
||||
@ -935,7 +915,7 @@ class P10Protocol(IRCS2SProtocol):
|
||||
def handle_nick(self, source, command, args):
|
||||
"""Handles the NICK command, used for user introductions and nick changes."""
|
||||
if len(args) > 2:
|
||||
# <- AB N jlu5 1 1460673049 ~jlu5 nefarious.midnight.vpn +iw B]AAAB ABAAA :realname
|
||||
# <- AB N GL 1 1460673049 ~gl nefarious.midnight.vpn +iw B]AAAB ABAAA :realname
|
||||
|
||||
nick = args[0]
|
||||
self._check_nick_collision(nick)
|
||||
@ -952,7 +932,6 @@ class P10Protocol(IRCS2SProtocol):
|
||||
ident, host, realname, realhost, ip)
|
||||
|
||||
uobj = self.users[uid] = User(self, nick, ts, uid, source, ident, host, realname, realhost, ip)
|
||||
uobj.ssl = False
|
||||
self.servers[source].users.add(uid)
|
||||
|
||||
# https://github.com/evilnet/nefarious2/blob/master/doc/p10.txt#L708
|
||||
@ -970,18 +949,15 @@ class P10Protocol(IRCS2SProtocol):
|
||||
accountname = modepair[1].split(':', 1)[0]
|
||||
self.call_hooks([uid, 'CLIENT_SERVICES_LOGIN', {'text': accountname}])
|
||||
|
||||
elif modepair[0][-1] == self.umodes.get('ssl'): # track SSL status where available
|
||||
uobj.ssl = True
|
||||
|
||||
# Call the OPERED UP hook if +o is being added to the mode list.
|
||||
self._check_oper_status_change(uid, parsedmodes)
|
||||
|
||||
self._check_cloak_change(uid)
|
||||
|
||||
return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip, 'parse_as': 'UID', 'secure': uobj.ssl}
|
||||
return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip, 'parse_as': 'UID'}
|
||||
|
||||
else:
|
||||
# <- ABAAA N jlu5_ 1460753763
|
||||
# <- ABAAA N GL_ 1460753763
|
||||
oldnick = self.users[source].nick
|
||||
newnick = self.users[source].nick = args[0]
|
||||
|
||||
@ -1061,7 +1037,7 @@ class P10Protocol(IRCS2SProtocol):
|
||||
# -> X3 Z Channels.CollectiveIRC.Net 1460745823.89510 0 1460745823.089840
|
||||
# Arguments of a PONG: our server hostname, the original TS of PING,
|
||||
# difference between PING and PONG in seconds, the current TS.
|
||||
# Why is this the way it is? I don't know... -jlu5
|
||||
# Why is this the way it is? I don't know... -GL
|
||||
|
||||
target = args[1]
|
||||
sid = self._get_SID(target)
|
||||
@ -1237,7 +1213,7 @@ class P10Protocol(IRCS2SProtocol):
|
||||
|
||||
def handle_topic(self, source, command, args):
|
||||
"""Handles TOPIC changes."""
|
||||
# <- ABAAA T #test jlu5!~jlu5@nefarious.midnight.vpn 1460852591 1460855795 :blah
|
||||
# <- ABAAA T #test GL!~gl@nefarious.midnight.vpn 1460852591 1460855795 :blah
|
||||
channel = args[0]
|
||||
topic = args[-1]
|
||||
|
||||
@ -1290,7 +1266,7 @@ class P10Protocol(IRCS2SProtocol):
|
||||
target = args[0]
|
||||
|
||||
if self.serverdata.get('use_extended_accounts'):
|
||||
# Registration: <- AA AC ABAAA R jlu5 1459019072
|
||||
# Registration: <- AA AC ABAAA R GL 1459019072
|
||||
# Logout: <- AA AC ABAAA U
|
||||
|
||||
# 1 <target user numeric>
|
||||
@ -1299,16 +1275,11 @@ class P10Protocol(IRCS2SProtocol):
|
||||
|
||||
# Any other subcommands listed at https://github.com/evilnet/nefarious2/blob/master/doc/p10.txt#L354
|
||||
# shouldn't apply to us.
|
||||
|
||||
if args[1] in ('R', 'M'):
|
||||
accountname = args[2]
|
||||
elif args[1] == 'U':
|
||||
accountname = '' # logout
|
||||
elif len(args[1]) > 1:
|
||||
log.warning('(%s) Got subcommand %r for %s in ACCOUNT message, is use_extended_accounts set correctly?',
|
||||
self.name, args[1], target)
|
||||
return
|
||||
else:
|
||||
return
|
||||
|
||||
else:
|
||||
# ircu or nefarious with F:EXTENDED_ACCOUNTS = FALSE
|
||||
|
@ -8,9 +8,7 @@ import time
|
||||
from pylinkirc import conf, utils
|
||||
from pylinkirc.classes import *
|
||||
from pylinkirc.log import log
|
||||
from pylinkirc.protocols.ts6_common import TS6BaseProtocol
|
||||
|
||||
__all__ = ['TS6Protocol']
|
||||
from pylinkirc.protocols.ts6_common import *
|
||||
|
||||
|
||||
class TS6Protocol(TS6BaseProtocol):
|
||||
@ -23,7 +21,7 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
else 'charybdis')
|
||||
self._ircd = self._ircd.lower()
|
||||
if self._ircd not in self.SUPPORTED_IRCDS:
|
||||
log.warning("(%s) Unsupported IRCd %r; falling back to 'charybdis' instead", self.name, self._ircd)
|
||||
log.warning("(%s) Unsupported IRCd %r; falling back to 'charybdis' instead", self.name, target_ircd)
|
||||
self._ircd = 'charybdis'
|
||||
|
||||
self._can_chghost = False
|
||||
@ -117,15 +115,6 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
self._channels[channel].users.add(client)
|
||||
self.users[client].channels.add(channel)
|
||||
|
||||
def oper_notice(self, source, text):
|
||||
"""
|
||||
Send a message to all opers.
|
||||
"""
|
||||
if self.is_internal_server(source):
|
||||
# Charybdis TS6 only allows OPERWALL from users
|
||||
source = self.pseudoclient.uid
|
||||
self._send_with_prefix(source, 'OPERWALL :%s' % text)
|
||||
|
||||
def sjoin(self, server, channel, users, ts=None, modes=set()):
|
||||
"""Sends an SJOIN for a group of users to a channel.
|
||||
|
||||
@ -326,19 +315,19 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
'quiet': 'q', 'redirect': 'f', 'freetarget': 'F',
|
||||
'joinflood': 'j', 'largebanlist': 'L', 'permanent': 'P',
|
||||
'noforwards': 'Q', 'stripcolor': 'c', 'allowinvite':
|
||||
'g', 'opmoderated': 'z', 'noctcp': 'C',
|
||||
'g', 'opmoderated': 'z', 'noctcp': 'C', 'ssl': 'Z',
|
||||
# charybdis modes provided by extensions
|
||||
'operonly': 'O', 'adminonly': 'A', 'sslonly': 'S',
|
||||
'nonotice': 'T',
|
||||
'*A': 'beIq', '*B': 'k', '*C': 'lfj', '*D': 'mnprstFLPQcgzCOAST'
|
||||
'*A': 'beIq', '*B': 'k', '*C': 'lfj', '*D': 'mnprstFLPQcgzCZOAST'
|
||||
})
|
||||
self.umodes.update({
|
||||
'deaf': 'D', 'servprotect': 'S', 'admin': 'a',
|
||||
'invisible': 'i', 'oper': 'o', 'wallops': 'w',
|
||||
'snomask': 's', 'noforward': 'Q', 'regdeaf': 'R',
|
||||
'callerid': 'g', 'operwall': 'z', 'locops': 'l',
|
||||
'cloak': 'x', 'override': 'p', 'ssl': 'Z',
|
||||
'*A': '', '*B': '', '*C': '', '*D': 'DSaiowsQRgzlxpZ'
|
||||
'cloak': 'x', 'override': 'p',
|
||||
'*A': '', '*B': '', '*C': '', '*D': 'DSaiowsQRgzlxp'
|
||||
})
|
||||
|
||||
# Charybdis extbans
|
||||
@ -386,7 +375,7 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
self.cmodes.update(chatircd_cmodes)
|
||||
self.cmodes['*D'] += ''.join(chatircd_cmodes.values())
|
||||
|
||||
chatircd_umodes = {'netadmin': 'n', 'bot': 'B', 'sslonlymsg': 't'}
|
||||
chatircd_umodes = {'netadmin': 'n', 'bot': 'B', 'callerid_sslonly': 't'}
|
||||
self.umodes.update(chatircd_umodes)
|
||||
self.umodes['*D'] += ''.join(chatircd_umodes.values())
|
||||
|
||||
@ -440,7 +429,7 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
|
||||
if args[0] != self.serverdata['recvpass']:
|
||||
# Check if recvpass is correct
|
||||
raise ProtocolError('Recvpass from uplink server %r does not match configuration!' % numeric)
|
||||
raise ProtocolError('Recvpass from uplink server %s does not match configuration!' % servername)
|
||||
|
||||
if args[1] != 'TS' and args[2] != '6':
|
||||
raise ProtocolError("Remote protocol version is too old! Is this even TS6?")
|
||||
@ -584,7 +573,7 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
|
||||
def handle_euid(self, numeric, command, args):
|
||||
"""Handles incoming EUID commands (user introduction)."""
|
||||
# <- :42X EUID jlu5 1 1437505322 +ailoswz ~jlu5 127.0.0.1 127.0.0.1 42XAAAAAB * * :realname
|
||||
# <- :42X EUID GL 1 1437505322 +ailoswz ~gl 127.0.0.1 127.0.0.1 42XAAAAAB * * :realname
|
||||
nick = args[0]
|
||||
self._check_nick_collision(nick)
|
||||
ts, modes, ident, host, ip, uid, realhost, accountname, realname = args[2:11]
|
||||
@ -614,11 +603,7 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
if accountname != "*":
|
||||
self.call_hooks([uid, 'CLIENT_SERVICES_LOGIN', {'text': accountname}])
|
||||
|
||||
# charybdis and derivatives have a usermode (+Z) to mark SSL connections
|
||||
# ratbox doesn't appear to have this
|
||||
has_ssl = self.users[uid].ssl = ('+%s' % self.umodes.get('ssl'), None) in parsedmodes
|
||||
|
||||
return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip, 'secure': has_ssl}
|
||||
return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip}
|
||||
|
||||
def handle_uid(self, numeric, command, args):
|
||||
"""Handles legacy user introductions (UID)."""
|
||||
@ -657,7 +642,7 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
|
||||
return
|
||||
|
||||
# <- :services.int SERVER a.bc 2 :(H) [jlu5] a
|
||||
# <- :services.int SERVER a.bc 2 :(H) [GL] a
|
||||
return super().handle_server(numeric, command, args)
|
||||
|
||||
def handle_tmode(self, numeric, command, args):
|
||||
@ -675,7 +660,7 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
|
||||
def handle_tb(self, numeric, command, args):
|
||||
"""Handles incoming topic burst (TB) commands."""
|
||||
# <- :42X TB #chat 1467427448 jlu5!~jlu5@127.0.0.1 :test
|
||||
# <- :42X TB #chat 1467427448 GL!~gl@127.0.0.1 :test
|
||||
channel = args[0]
|
||||
ts = args[1]
|
||||
setter = args[2]
|
||||
@ -686,7 +671,7 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
|
||||
def handle_etb(self, numeric, command, args):
|
||||
"""Handles extended topic burst (ETB)."""
|
||||
# <- :00AAAAAAC ETB 0 #test 1470021157 jlu5 :test | abcd
|
||||
# <- :00AAAAAAC ETB 0 #test 1470021157 GL :test | abcd
|
||||
# Same as TB, with extra TS and extensions arguments.
|
||||
channel = args[1]
|
||||
ts = args[2]
|
||||
@ -723,7 +708,7 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
the administrator that certain extensions should be loaded for the best
|
||||
compatibility.
|
||||
"""
|
||||
# <- :charybdis.midnight.vpn 472 jlu5|devel O :is an unknown mode char to me
|
||||
# <- :charybdis.midnight.vpn 472 GL|devel O :is an unknown mode char to me
|
||||
badmode = args[1]
|
||||
reason = args[-1]
|
||||
setter = args[0]
|
||||
@ -740,7 +725,7 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
"""
|
||||
Handles SU, which is used for setting login information.
|
||||
"""
|
||||
# <- :00A ENCAP * SU 42XAAAAAC :jlu5
|
||||
# <- :00A ENCAP * SU 42XAAAAAC :GLolol
|
||||
# <- :00A ENCAP * SU 42XAAAAAC
|
||||
try:
|
||||
account = args[1] # Account name is being set
|
||||
@ -759,7 +744,7 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
|
||||
def handle_realhost(self, uid, command, args):
|
||||
"""Handles real host propagation."""
|
||||
log.debug('(%s) Got REALHOST %s for %s', self.name, args[0], uid)
|
||||
log.debug('(%s) Got REALHOST %s for %s', args[0], uid)
|
||||
self.users[uid].realhost = args[0]
|
||||
|
||||
def handle_login(self, uid, command, args):
|
||||
|
@ -5,13 +5,11 @@ ts6_common.py: Common base protocol class with functions shared by the UnrealIRC
|
||||
import string
|
||||
import time
|
||||
|
||||
from pylinkirc import conf, structures
|
||||
from pylinkirc import conf, structures, utils
|
||||
from pylinkirc.classes import *
|
||||
from pylinkirc.log import log
|
||||
from pylinkirc.protocols.ircs2s_common import *
|
||||
|
||||
__all__ = ['TS6BaseProtocol']
|
||||
|
||||
|
||||
class TS6SIDGenerator():
|
||||
"""
|
||||
@ -87,16 +85,18 @@ class TS6SIDGenerator():
|
||||
sid = ''.join(self.output)
|
||||
return sid
|
||||
|
||||
class TS6UIDGenerator(UIDGenerator):
|
||||
class TS6UIDGenerator(IncrementalUIDGenerator):
|
||||
"""Implements an incremental TS6 UID Generator."""
|
||||
|
||||
def __init__(self, sid):
|
||||
# Define the options for IncrementalUIDGenerator, and then
|
||||
# initialize its functions.
|
||||
# TS6 UIDs are 6 characters in length (9 including the SID).
|
||||
# They go from ABCDEFGHIJKLMNOPQRSTUVWXYZ -> 0123456789 -> wrap around:
|
||||
# e.g. AAAAAA, AAAAAB ..., AAAAA8, AAAAA9, AAAABA, etc.
|
||||
uidchars = string.ascii_uppercase + string.digits
|
||||
length = 6
|
||||
super().__init__(uidchars, length, sid)
|
||||
self.allowedchars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456879'
|
||||
self.length = 6
|
||||
super().__init__(sid)
|
||||
|
||||
class TS6BaseProtocol(IRCS2SProtocol):
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -216,7 +216,7 @@ class TS6BaseProtocol(IRCS2SProtocol):
|
||||
|
||||
def handle_nick(self, numeric, command, args):
|
||||
"""Handles incoming NICK changes."""
|
||||
# <- :70MAAAAAA NICK jlu5-devel 1434744242
|
||||
# <- :70MAAAAAA NICK GL-devel 1434744242
|
||||
oldnick = self.users[numeric].nick
|
||||
newnick = self.users[numeric].nick = args[0]
|
||||
|
||||
@ -247,7 +247,7 @@ class TS6BaseProtocol(IRCS2SProtocol):
|
||||
|
||||
def handle_server(self, numeric, command, args):
|
||||
"""Handles the SERVER command, used for introducing older (TS5) servers."""
|
||||
# <- :services.int SERVER a.bc 2 :(H) [jlu5] test jupe
|
||||
# <- :services.int SERVER a.bc 2 :(H) [GL] test jupe
|
||||
servername = args[0].lower()
|
||||
sdesc = args[-1]
|
||||
self.servers[servername] = Server(self, numeric, servername, desc=sdesc)
|
||||
@ -270,5 +270,5 @@ class TS6BaseProtocol(IRCS2SProtocol):
|
||||
# This is rewritten to SVSNICK with args ['902AAAAAB', 'Guest53593', '1468299404']
|
||||
|
||||
# UnrealIRCd:
|
||||
# <- :services.midnight.vpn SVSNICK jlu5 Guest87795 1468303726
|
||||
# <- :services.midnight.vpn SVSNICK GL Guest87795 1468303726
|
||||
return {'target': self._get_UID(args[0]), 'newnick': args[1]}
|
||||
|
@ -1,5 +1,5 @@
|
||||
"""
|
||||
unreal.py: UnrealIRCd 4.x-5.x protocol module for PyLink.
|
||||
unreal.py: UnrealIRCd 4.x protocol module for PyLink.
|
||||
"""
|
||||
|
||||
import codecs
|
||||
@ -10,10 +10,7 @@ import time
|
||||
from pylinkirc import conf, utils
|
||||
from pylinkirc.classes import *
|
||||
from pylinkirc.log import log
|
||||
from pylinkirc.protocols.ts6_common import TS6BaseProtocol
|
||||
|
||||
__all__ = ['UnrealProtocol']
|
||||
|
||||
from pylinkirc.protocols.ts6_common import *
|
||||
|
||||
SJOIN_PREFIXES = {'q': '*', 'a': '~', 'o': '@', 'h': '%', 'v': '+', 'b': '&', 'e': '"', 'I': "'"}
|
||||
|
||||
@ -21,7 +18,7 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
# I'm not sure what the real limit is, but the text posted at
|
||||
# https://github.com/jlu5/PyLink/issues/378 suggests 427 characters.
|
||||
# https://github.com/unrealircd/unrealircd/blob/4cad9cb/src/modules/m_server.c#L1260 may
|
||||
# also help. (but why BUFSIZE-*80*?) -jlu5
|
||||
# also help. (but why BUFSIZE-*80*?) -GL
|
||||
S2S_BUFSIZE = 427
|
||||
_KNOWN_CMODES = {'ban': 'b',
|
||||
'banexception': 'e',
|
||||
@ -55,7 +52,6 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
'topiclock': 't',
|
||||
'voice': 'v'}
|
||||
_KNOWN_UMODES = {'bot': 'B',
|
||||
'censor': 'G',
|
||||
'cloak': 'x',
|
||||
'deaf': 'd',
|
||||
'filter': 'G',
|
||||
@ -67,7 +63,6 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
'protected': 'q',
|
||||
'regdeaf': 'R',
|
||||
'registered': 'r',
|
||||
'sslonlymsg': 'Z',
|
||||
'servprotect': 'S',
|
||||
'showwhois': 'W',
|
||||
'snomask': 's',
|
||||
@ -149,7 +144,7 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
# Now, strip the trailing \n and decode into a string again.
|
||||
encoded_ip = encoded_ip.strip().decode()
|
||||
|
||||
# <- :001 UID jlu5 0 1441306929 jlu5 localhost 0018S7901 0 +iowx * midnight-1C620195 fwAAAQ== :realname
|
||||
# <- :001 UID GL 0 1441306929 gl localhost 0018S7901 0 +iowx * midnight-1C620195 fwAAAQ== :realname
|
||||
self._send_with_prefix(server, "UID {nick} {hopcount} {ts} {ident} {realhost} {uid} 0 {modes} "
|
||||
"{host} * {ip} :{realname}".format(ts=ts, host=host,
|
||||
nick=nick, ident=ident, uid=uid,
|
||||
@ -259,7 +254,7 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
Sends mode changes from a PyLink client/server. The mode list should be
|
||||
a list of (mode, arg) tuples, i.e. the format of utils.parse_modes() output.
|
||||
"""
|
||||
# <- :unreal.midnight.vpn MODE #test +ntCo jlu5 1444361345
|
||||
# <- :unreal.midnight.vpn MODE #test +ntCo GL 1444361345
|
||||
|
||||
if (not self.is_internal_client(numeric)) and \
|
||||
(not self.is_internal_server(numeric)):
|
||||
@ -312,12 +307,6 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
joinedmodes = self.join_modes(modes)
|
||||
self._send_with_prefix(target, 'UMODE2 %s' % joinedmodes)
|
||||
|
||||
def oper_notice(self, source, text):
|
||||
"""
|
||||
Send a message to all opers.
|
||||
"""
|
||||
self._send_with_prefix(source, 'GLOBOPS :%s' % text)
|
||||
|
||||
def set_server_ban(self, source, duration, user='*', host='*', reason='User banned'):
|
||||
"""
|
||||
Sets a server ban.
|
||||
@ -381,21 +370,11 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
self.call_hooks([self.sid, 'CHGNAME',
|
||||
{'target': target, 'newgecos': text}])
|
||||
|
||||
def kill(self, source, target, reason):
|
||||
"""Sends a kill from a PyLink client or server."""
|
||||
|
||||
if (not self.is_internal_client(source)) and \
|
||||
(not self.is_internal_server(source)):
|
||||
raise LookupError('No such PyLink client/server exists.')
|
||||
|
||||
self._send_with_prefix(source, 'KILL %s :%s' % (target, reason))
|
||||
self._remove_client(target)
|
||||
|
||||
def knock(self, numeric, target, text):
|
||||
"""Sends a KNOCK from a PyLink client."""
|
||||
# KNOCKs in UnrealIRCd are actually just specially formatted NOTICEs,
|
||||
# sent to all ops in a channel.
|
||||
# <- :unreal.midnight.vpn NOTICE @#test :[Knock] by jlu5|!jlu5@hidden-1C620195 (test)
|
||||
# <- :unreal.midnight.vpn NOTICE @#test :[Knock] by GL|!gl@hidden-1C620195 (test)
|
||||
assert self.is_channel(target), "Can only knock on channels!"
|
||||
sender = self.get_server(numeric)
|
||||
s = '[Knock] by %s (%s)' % (self.get_hostmask(numeric), text)
|
||||
@ -433,8 +412,7 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
# not work for any UnrealIRCd 3.2 users.
|
||||
# ESVID - Supports account names in services stamps instead of just the signon time.
|
||||
# AFAIK this doesn't actually affect services' behaviour?
|
||||
# EXTSWHOIS - support multiple SWHOIS lines (purely informational for us)
|
||||
f('PROTOCTL SJOIN SJ3 NOQUIT NICKv2 VL UMODE2 PROTOCTL NICKIP EAUTH=%s SID=%s VHP ESVID EXTSWHOIS' % (self.serverdata["hostname"], self.sid))
|
||||
f('PROTOCTL SJOIN SJ3 NOQUIT NICKv2 VL UMODE2 PROTOCTL NICKIP EAUTH=%s SID=%s VHP ESVID' % (self.serverdata["hostname"], self.sid))
|
||||
sdesc = self.serverdata.get('serverdesc') or conf.conf['pylink']['serverdesc']
|
||||
f('SERVER %s 1 U%s-h6e-%s :%s' % (host, self.proto_ver, self.sid, sdesc))
|
||||
|
||||
@ -468,8 +446,8 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
return {}
|
||||
|
||||
def handle_uid(self, numeric, command, args):
|
||||
# <- :001 UID jlu5 0 1441306929 jlu5 localhost 0018S7901 0 +iowx * midnight-1C620195 fwAAAQ== :realname
|
||||
# <- :001 UID jlu5| 0 1441389007 jlu5 10.120.0.6 001ZO8F03 0 +iwx * 391A9CB9.26A16454.D9847B69.IP CngABg== :realname
|
||||
# <- :001 UID GL 0 1441306929 gl localhost 0018S7901 0 +iowx * midnight-1C620195 fwAAAQ== :realname
|
||||
# <- :001 UID GL| 0 1441389007 gl 10.120.0.6 001ZO8F03 0 +iwx * 391A9CB9.26A16454.D9847B69.IP CngABg== :realname
|
||||
# arguments: nick, hopcount?, ts, ident, real-host, UID, services account (0 if none), modes,
|
||||
# displayed host, cloaked (+x) host, base64-encoded IP, and realname
|
||||
nick = args[0]
|
||||
@ -524,16 +502,13 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
if ('+r', None) in parsedmodes and accountname.isdigit():
|
||||
accountname = nick
|
||||
|
||||
# Track SSL/TLS status
|
||||
has_ssl = self.users[uid].ssl = ('+z', None) in parsedmodes
|
||||
|
||||
if not accountname.isdigit():
|
||||
self.call_hooks([uid, 'CLIENT_SERVICES_LOGIN', {'text': accountname}])
|
||||
|
||||
# parse_as is used here to prevent legacy user introduction from being confused
|
||||
# with a nick change.
|
||||
return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host,
|
||||
'ident': ident, 'ip': ip, 'parse_as': 'UID', 'secure': has_ssl}
|
||||
'ident': ident, 'ip': ip, 'parse_as': 'UID'}
|
||||
|
||||
def handle_pass(self, numeric, command, args):
|
||||
# <- PASS :abcdefg
|
||||
@ -559,7 +534,7 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
|
||||
sdesc = args[-1].split(" ", 1)
|
||||
# Get our protocol version. I really don't know why the version and the server
|
||||
# description aren't two arguments instead of one... -jlu5
|
||||
# description aren't two arguments instead of one... -GLolol
|
||||
vline = sdesc[0].split('-', 1)
|
||||
sdesc = " ".join(sdesc[1:])
|
||||
|
||||
@ -581,7 +556,7 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
self.umodes['*D'] = ''.join(self._KNOWN_UMODES.values())
|
||||
else:
|
||||
# Legacy (non-SID) servers can still be introduced using the SERVER command.
|
||||
# <- :services.int SERVER a.bc 2 :(H) [jlu5] a
|
||||
# <- :services.int SERVER a.bc 2 :(H) [GL] a
|
||||
return super().handle_server(numeric, command, args)
|
||||
|
||||
def handle_protoctl(self, numeric, command, args):
|
||||
@ -621,7 +596,7 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
|
||||
def handle_join(self, numeric, command, args):
|
||||
"""Handles the UnrealIRCd JOIN command."""
|
||||
# <- :jlu5 JOIN #pylink,#test
|
||||
# <- :GL JOIN #pylink,#test
|
||||
if args[0] == '0':
|
||||
# /join 0; part the user from all channels
|
||||
oldchans = self.users[numeric].channels.copy()
|
||||
@ -735,9 +710,9 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
# <- NICK Global 3 1456843578 services novernet.com services.novernet.com 0 +ioS * :Global Noticer
|
||||
# & nick hopcount timestamp username hostname server service-identifier-token :realname
|
||||
# With NICKIP and VHP enabled:
|
||||
# <- NICK legacy32 2 1470699865 jlu5 localhost unreal32.midnight.vpn jlu5 +iowx hidden-1C620195 AAAAAAAAAAAAAAAAAAAAAQ== :realname
|
||||
# <- NICK GL32 2 1470699865 gl localhost unreal32.midnight.vpn GL +iowx hidden-1C620195 AAAAAAAAAAAAAAAAAAAAAQ== :realname
|
||||
# to this:
|
||||
# <- :001 UID jlu5 0 1441306929 jlu5 localhost 0018S7901 0 +iowx * hidden-1C620195 fwAAAQ== :realname
|
||||
# <- :001 UID GL 0 1441306929 gl localhost 0018S7901 0 +iowx * hidden-1C620195 fwAAAQ== :realname
|
||||
log.debug('(%s) got legacy NICK args: %s', self.name, ' '.join(args))
|
||||
|
||||
new_args = args[:] # Clone the old args list
|
||||
@ -759,15 +734,15 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
return self.handle_uid(servername, 'UID_LEGACY', new_args)
|
||||
else:
|
||||
# Normal NICK change, just let ts6_common handle it.
|
||||
# :70MAAAAAA NICK jlu5-devel 1434744242
|
||||
# :70MAAAAAA NICK GL-devel 1434744242
|
||||
return super().handle_nick(numeric, command, args)
|
||||
|
||||
def handle_mode(self, numeric, command, args):
|
||||
# <- :unreal.midnight.vpn MODE #test +bb test!*@* *!*@bad.net
|
||||
# <- :unreal.midnight.vpn MODE #test +q jlu5 1444361345
|
||||
# <- :unreal.midnight.vpn MODE #test +ntCo jlu5 1444361345
|
||||
# <- :unreal.midnight.vpn MODE #test +mntClfo 5 [10t]:5 jlu5 1444361345
|
||||
# <- :jlu5 MODE #services +v jlu5
|
||||
# <- :unreal.midnight.vpn MODE #test +q GL 1444361345
|
||||
# <- :unreal.midnight.vpn MODE #test +ntCo GL 1444361345
|
||||
# <- :unreal.midnight.vpn MODE #test +mntClfo 5 [10t]:5 GL 1444361345
|
||||
# <- :GL MODE #services +v GL
|
||||
|
||||
# This seems pretty relatively inconsistent - why do some commands have a TS at the end while others don't?
|
||||
# Answer: the first syntax (MODE sent by SERVER) is used for channel bursts - according to Unreal 3.2 docs,
|
||||
@ -856,30 +831,30 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
# which is supported by atheme and Anope 2.x).
|
||||
|
||||
# Logging in (with account info, atheme):
|
||||
# <- :NickServ SVS2MODE jlu5 +rd jlu5
|
||||
# <- :NickServ SVS2MODE GL +rd GL
|
||||
|
||||
# Logging in (without account info, anope 2.0?):
|
||||
# <- :NickServ SVS2MODE 001WCO6YK +r
|
||||
|
||||
# Logging in (without account info, anope 1.8):
|
||||
# Note: ignore the timestamp.
|
||||
# <- :services.abc.net SVS2MODE jlu5 +rd 1470696723
|
||||
# <- :services.abc.net SVS2MODE GLolol +rd 1470696723
|
||||
|
||||
# Logging out (atheme):
|
||||
# <- :NickServ SVS2MODE jlu5 -r+d 0
|
||||
# <- :NickServ SVS2MODE GL -r+d 0
|
||||
|
||||
# Logging out (anope 1.8):
|
||||
# <- :services.abc.net SVS2MODE jlu5 -r+d 1
|
||||
# <- :services.abc.net SVS2MODE GLolol -r+d 1
|
||||
|
||||
# Logging out (anope 2.0):
|
||||
# <- :NickServ SVS2MODE 009EWLA03 -r
|
||||
|
||||
# Logging in to account from a different nick (atheme):
|
||||
# Note: no +r is being set.
|
||||
# <- :NickServ SVS2MODE somenick +d jlu5
|
||||
# <- :NickServ SVS2MODE somenick +d GL
|
||||
|
||||
# Logging in to account from a different nick (anope):
|
||||
# <- :NickServ SVS2MODE 001SALZ01 +d jlu5
|
||||
# <- :NickServ SVS2MODE 001SALZ01 +d GL
|
||||
# <- :NickServ SVS2MODE 001SALZ01 +r
|
||||
|
||||
target = self._get_UID(args[0])
|
||||
@ -937,13 +912,13 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
|
||||
def handle_umode2(self, source, command, args):
|
||||
"""Handles UMODE2, used to set user modes on oneself."""
|
||||
# <- :jlu5 UMODE2 +W
|
||||
# <- :GL UMODE2 +W
|
||||
target = self._get_UID(source)
|
||||
return self._handle_umode(target, self.parse_modes(target, args))
|
||||
|
||||
def handle_topic(self, numeric, command, args):
|
||||
"""Handles the TOPIC command."""
|
||||
# <- jlu5 TOPIC #services jlu5 1444699395 :weeee
|
||||
# <- GL TOPIC #services GL 1444699395 :weeee
|
||||
# <- TOPIC #services devel.relay 1452399682 :test
|
||||
channel = args[0]
|
||||
topic = args[-1]
|
||||
@ -980,32 +955,17 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
self.users[numeric].realname = newgecos = args[0]
|
||||
return {'target': numeric, 'newgecos': newgecos}
|
||||
|
||||
def handle_chgident(self, source, command, args):
|
||||
def handle_chgident(self, numeric, command, args):
|
||||
"""Handles CHGIDENT, used for denoting ident changes."""
|
||||
# <- :jlu5 CHGIDENT jlu5 test
|
||||
# <- :GL CHGIDENT GL test
|
||||
target = self._get_UID(args[0])
|
||||
|
||||
# Bounce attempts to change fields of protected PyLink clients
|
||||
if self.is_internal_client(target):
|
||||
log.warning("(%s) Bouncing attempt from %s to change ident of PyLink client %s",
|
||||
self.name, self.get_friendly_name(source), self.get_friendly_name(target))
|
||||
self.update_client(target, 'IDENT', self.users[target].ident)
|
||||
return
|
||||
|
||||
self.users[target].ident = newident = args[1]
|
||||
return {'target': target, 'newident': newident}
|
||||
|
||||
def handle_chghost(self, source, command, args):
|
||||
def handle_chghost(self, numeric, command, args):
|
||||
"""Handles CHGHOST, used for denoting hostname changes."""
|
||||
# <- :jlu5 CHGHOST jlu5 some.host
|
||||
# <- :GL CHGHOST GL some.host
|
||||
target = self._get_UID(args[0])
|
||||
# Bounce attempts to change fields of protected PyLink clients
|
||||
if self.is_internal_client(target):
|
||||
log.warning("(%s) Bouncing attempt from %s to change host of PyLink client %s",
|
||||
self.name, self.get_friendly_name(source), self.get_friendly_name(target))
|
||||
self.update_client(target, 'HOST', self.users[target].host)
|
||||
return
|
||||
|
||||
self.users[target].host = newhost = args[1]
|
||||
|
||||
# When SETHOST or CHGHOST is used, modes +xt are implicitly set on the
|
||||
@ -1014,23 +974,16 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
|
||||
return {'target': target, 'newhost': newhost}
|
||||
|
||||
def handle_chgname(self, source, command, args):
|
||||
def handle_chgname(self, numeric, command, args):
|
||||
"""Handles CHGNAME, used for denoting real name/gecos changes."""
|
||||
# <- :jlu5 CHGNAME jlu5 :afdsafasf
|
||||
# <- :GL CHGNAME GL :afdsafasf
|
||||
target = self._get_UID(args[0])
|
||||
# Bounce attempts to change fields of protected PyLink clients
|
||||
if self.is_internal_client(target):
|
||||
log.warning("(%s) Bouncing attempt from %s to change gecos of PyLink client %s",
|
||||
self.name, self.get_friendly_name(source), self.get_friendly_name(target))
|
||||
self.update_client(target, 'REALNAME', self.users[target].realname)
|
||||
return
|
||||
|
||||
self.users[target].realname = newgecos = args[1]
|
||||
return {'target': target, 'newgecos': newgecos}
|
||||
|
||||
def handle_tsctl(self, source, command, args):
|
||||
"""Handles /TSCTL alltime requests."""
|
||||
# <- :jlu5 TSCTL alltime
|
||||
# <- :GL TSCTL alltime
|
||||
|
||||
if args[0] == 'alltime':
|
||||
self._send_with_prefix(self.sid, 'NOTICE %s :*** Server=%s time()=%d' % (source, self.hostname(), time.time()))
|
||||
|
@ -1,4 +0,0 @@
|
||||
cachetools
|
||||
passlib
|
||||
pyyaml
|
||||
setuptools
|
@ -9,9 +9,6 @@ import threading
|
||||
from pylinkirc import world
|
||||
from pylinkirc.log import log
|
||||
|
||||
__all__ = ['register', 'unregister', 'start']
|
||||
|
||||
|
||||
SELECT_TIMEOUT = 0.5
|
||||
|
||||
selector = selectors.DefaultSelector()
|
||||
|
19
setup.py
19
setup.py
@ -4,8 +4,8 @@ import subprocess
|
||||
import sys
|
||||
from codecs import open
|
||||
|
||||
if sys.version_info < (3, 7):
|
||||
raise RuntimeError("PyLink requires Python 3.7 or higher.")
|
||||
if sys.version_info < (3, 4):
|
||||
raise RuntimeError("PyLink requires Python 3.4 or higher.")
|
||||
|
||||
try:
|
||||
from setuptools import setup, find_packages
|
||||
@ -30,8 +30,14 @@ with open('__init__.py', 'w') as f:
|
||||
f.write('real_version = %r\n' % real_version)
|
||||
|
||||
try:
|
||||
with open('README.md') as f:
|
||||
long_description = f.read()
|
||||
if sys.version_info >= (3, 5):
|
||||
with open('README.md') as f:
|
||||
long_description = f.read()
|
||||
else:
|
||||
# Work around "TypeError: a bytes-like object is required, not 'str'" errors on Python 3.4
|
||||
# when the README has Unicode characters (error in distutils.util.rfc822_escape)
|
||||
import codecs
|
||||
long_description = codecs.open('README.md', encoding='utf-8').read()
|
||||
except OSError:
|
||||
print('WARNING: Failed to read readme, skipping writing long_description')
|
||||
long_description = None
|
||||
@ -70,10 +76,9 @@ setup(
|
||||
'Natural Language :: English',
|
||||
|
||||
'Programming Language :: Python :: 3 :: Only',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
],
|
||||
|
||||
keywords='IRC services relay',
|
||||
|
@ -16,13 +16,6 @@ from copy import copy, deepcopy
|
||||
from . import conf
|
||||
from .log import log
|
||||
|
||||
__all__ = ['KeyedDefaultdict', 'CopyWrapper', 'CaseInsensitiveFixedSet',
|
||||
'CaseInsensitiveDict', 'IRCCaseInsensitiveDict',
|
||||
'CaseInsensitiveSet', 'IRCCaseInsensitiveSet',
|
||||
'CamelCaseToSnakeCase', 'DataStore', 'JSONDataStore',
|
||||
'PickleDataStore']
|
||||
|
||||
|
||||
_BLACKLISTED_COPY_TYPES = []
|
||||
|
||||
class KeyedDefaultdict(collections.defaultdict):
|
||||
@ -32,7 +25,7 @@ class KeyedDefaultdict(collections.defaultdict):
|
||||
def __missing__(self, key):
|
||||
if self.default_factory is None:
|
||||
# If there is no default factory, just let defaultdict handle it
|
||||
super().__missing__(key)
|
||||
super().__missing__(self, key)
|
||||
else:
|
||||
value = self[key] = self.default_factory(key)
|
||||
return value
|
||||
@ -53,7 +46,7 @@ class CopyWrapper():
|
||||
for attr, val in self.__dict__.items():
|
||||
# We can't pickle IRCNetwork, so just return a reference of it.
|
||||
if not isinstance(val, tuple(_BLACKLISTED_COPY_TYPES)):
|
||||
#log.debug('CopyWrapper: copying attr %r', attr)
|
||||
log.debug('CopyWrapper: copying attr %r', attr)
|
||||
setattr(newobj, attr, deepcopy(val))
|
||||
|
||||
memo[id(self)] = newobj
|
||||
@ -208,12 +201,7 @@ class DataStore:
|
||||
Generic database class. Plugins should use a subclass of this such as JSONDataStore or
|
||||
PickleDataStore.
|
||||
"""
|
||||
def __init__(self, name, filename, save_frequency=None, default_db=None, data_dir=None):
|
||||
if data_dir is None:
|
||||
data_dir = conf.conf['pylink'].get('data_dir', '')
|
||||
|
||||
filename = os.path.join(data_dir, filename)
|
||||
|
||||
def __init__(self, name, filename, save_frequency=None, default_db=None):
|
||||
self.name = name
|
||||
self.filename = filename
|
||||
self.tmp_filename = filename + '.tmp'
|
||||
|
@ -29,7 +29,7 @@ class DummySocket():
|
||||
return None
|
||||
'''
|
||||
|
||||
def recv(self, bufsize, *args):
|
||||
def recv(bufsize, *args):
|
||||
raise NotImplementedError
|
||||
|
||||
def send(self, data):
|
||||
@ -137,7 +137,7 @@ class BaseProtocolTest(unittest.TestCase):
|
||||
assertT('{RACKETman}')
|
||||
assertT('bar|tender')
|
||||
assertT('\\bar|bender\\')
|
||||
assertT('jlu5|ovd')
|
||||
assertT('GL|ovd')
|
||||
assertT('B')
|
||||
assertT('`')
|
||||
assertT('Hello123')
|
||||
@ -156,7 +156,7 @@ class BaseProtocolTest(unittest.TestCase):
|
||||
assertF('-test')
|
||||
assertF('#lounge')
|
||||
assertF('\\bar/bender\\')
|
||||
assertF('jlu5/ovd') # Technically not valid, but some IRCds don't care ;)
|
||||
assertF('GL/ovd') # Technically not valid, but some IRCds don't care ;)
|
||||
assertF('100AAAAAC') # TS6 UID
|
||||
|
||||
self.assertFalse(self.p.is_nick('longnicklongnicklongnicklongnicklongnicklongnick', nicklen=20))
|
||||
@ -343,7 +343,7 @@ class BaseProtocolTest(unittest.TestCase):
|
||||
[('+v', '100'), ('+i', None), ('+p', None)]
|
||||
)
|
||||
|
||||
def test_parse_modes_channel_rfc2(self):
|
||||
def test_parse_modes_channel_rfc(self):
|
||||
# These are basic tests that only use RFC 1459 defined modes.
|
||||
# IRCds supporting more complex modes can define new test cases if needed.
|
||||
c = self.p.channels['#testruns'] = Channel(self.p, name='#testruns')
|
||||
@ -575,13 +575,6 @@ class BaseProtocolTest(unittest.TestCase):
|
||||
self.p.apply_modes('#', [('+n', None), ('-i', None)])
|
||||
self.assertEqual(c.modes, {('n', None)})
|
||||
|
||||
c = self.p.channels['#Magic'] = Channel(self.p, name='#Magic')
|
||||
self.p.apply_modes('#Magic', [('+m', None), ('+n', None), ('+i', None)])
|
||||
self.assertEqual(c.modes, {('m', None), ('n', None), ('i', None)}, "Modes should be added")
|
||||
|
||||
self.p.apply_modes('#Magic', [('-i', None), ('-n', None)])
|
||||
self.assertEqual(c.modes, {('m', None)}, "Modes should be removed")
|
||||
|
||||
def test_apply_modes_channel_remove_nonexistent(self):
|
||||
c = self.p.channels['#abc'] = Channel(self.p, name='#abc')
|
||||
self.p.apply_modes('#abc', [('+t', None)])
|
||||
@ -641,15 +634,6 @@ class BaseProtocolTest(unittest.TestCase):
|
||||
self.p.apply_modes('#Magic', [('-b', '*!*@BEST.HOST')])
|
||||
self.assertFalse(c.modes, "Ban on *!*@best.host should be removed (different case)")
|
||||
|
||||
def test_apply_modes_channel_ban_multiple(self):
|
||||
c = self.p.channels['#Magic'] = Channel(self.p, name='#Magic')
|
||||
self.p.apply_modes('#Magic', [('+b', '*!*@test.host'), ('+b', '*!*@best.host'), ('+b', '*!*@guest.host')])
|
||||
self.assertEqual(c.modes, {('b', '*!*@test.host'), ('b', '*!*@best.host'), ('b', '*!*@guest.host')},
|
||||
"Bans should be added")
|
||||
|
||||
self.p.apply_modes('#Magic', [('-b', '*!*@best.host'), ('-b', '*!*@guest.host'), ('-b', '*!*@test.host')])
|
||||
self.assertEqual(c.modes, set(), "Bans should be removed")
|
||||
|
||||
def test_apply_modes_channel_mode_cycle(self):
|
||||
c = self.p.channels['#Magic'] = Channel(self.p, name='#Magic')
|
||||
self.p.apply_modes('#Magic', [('+b', '*!*@example.net'), ('-b', '*!*@example.net')])
|
||||
|
@ -4,6 +4,7 @@ Runs IRC parser tests from ircdocs/parser-tests.
|
||||
This test suite runs static code only.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import yaml
|
||||
@ -17,13 +18,20 @@ from pylinkirc.protocols.ircs2s_common import IRCCommonProtocol
|
||||
class MessageParserTest(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
with open(PARSER_DATA_PATH / 'msg-split.yaml') as f:
|
||||
|
||||
if sys.version_info >= (3, 6):
|
||||
_open = open
|
||||
else:
|
||||
def _open(f): # Coerse pathlib paths to str for py3.5 compat
|
||||
return open(str(f))
|
||||
|
||||
with _open(PARSER_DATA_PATH / 'msg-split.yaml') as f:
|
||||
cls.MESSAGE_SPLIT_TEST_DATA = yaml.safe_load(f)
|
||||
with open(PARSER_DATA_PATH / 'userhost-split.yaml') as f:
|
||||
with _open(PARSER_DATA_PATH / 'userhost-split.yaml') as f:
|
||||
cls.USER_HOST_SPLIT_TEST_DATA = yaml.safe_load(f)
|
||||
with open(PARSER_DATA_PATH / 'mask-match.yaml') as f:
|
||||
with _open(PARSER_DATA_PATH / 'mask-match.yaml') as f:
|
||||
cls.MASK_MATCH_TEST_DATA = yaml.safe_load(f)
|
||||
with open(PARSER_DATA_PATH / 'validate-hostname.yaml') as f:
|
||||
with _open(PARSER_DATA_PATH / 'validate-hostname.yaml') as f:
|
||||
cls.VALIDATE_HOSTNAME_TEST_DATA = yaml.safe_load(f)
|
||||
|
||||
def testMessageSplit(self):
|
||||
@ -103,16 +111,5 @@ class MessageParserTest(unittest.TestCase):
|
||||
|
||||
# N.B. skipping msg-join tests because PyLink doesn't think about messages that way
|
||||
|
||||
### Custom test cases
|
||||
def testMessageSplitSpaces(self):
|
||||
# Test that tokenization ignores empty fields, but doesn't strip away other types of whitespace
|
||||
f = IRCCommonProtocol.parse_prefixed_args
|
||||
self.assertEqual(f(":foo PRIVMSG #test :message"), ["foo", "PRIVMSG", "#test", "message"])
|
||||
self.assertEqual(f(":123LOLWUT NICK cursed\u3000nickname"), ["123LOLWUT", "NICK", "cursed\u3000nickname"])
|
||||
self.assertEqual(f(":123LOLWUT MODE ## +ov \x1f checking"),
|
||||
["123LOLWUT", "MODE", "##", "+ov", "\x1f", "checking"])
|
||||
self.assertEqual(f(":123LOLWUT MODE ## +ov \u3000 checking"),
|
||||
["123LOLWUT", "MODE", "##", "+ov", "\u3000", "checking"])
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -1,86 +0,0 @@
|
||||
"""
|
||||
Tests for protocols/p10
|
||||
"""
|
||||
|
||||
import unittest
|
||||
|
||||
from pylinkirc.protocols import p10
|
||||
|
||||
class P10UIDGeneratorTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.uidgen = p10.P10UIDGenerator('HI')
|
||||
|
||||
def test_initial_UID(self):
|
||||
expected = [
|
||||
"HIAAA",
|
||||
"HIAAB",
|
||||
"HIAAC",
|
||||
"HIAAD",
|
||||
"HIAAE",
|
||||
"HIAAF"
|
||||
]
|
||||
self.uidgen.counter = 0
|
||||
actual = [self.uidgen.next_uid() for i in range(6)]
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_rollover_first_lowercase(self):
|
||||
expected = [
|
||||
"HIAAY",
|
||||
"HIAAZ",
|
||||
"HIAAa",
|
||||
"HIAAb",
|
||||
"HIAAc",
|
||||
"HIAAd",
|
||||
]
|
||||
self.uidgen.counter = 24
|
||||
actual = [self.uidgen.next_uid() for i in range(6)]
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_rollover_first_num(self):
|
||||
expected = [
|
||||
"HIAAz",
|
||||
"HIAA0",
|
||||
"HIAA1",
|
||||
"HIAA2",
|
||||
"HIAA3",
|
||||
"HIAA4",
|
||||
]
|
||||
self.uidgen.counter = 26*2-1
|
||||
actual = [self.uidgen.next_uid() for i in range(6)]
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_rollover_second(self):
|
||||
expected = [
|
||||
"HIAA8",
|
||||
"HIAA9",
|
||||
"HIAA[",
|
||||
"HIAA]",
|
||||
"HIABA",
|
||||
"HIABB",
|
||||
"HIABC",
|
||||
"HIABD",
|
||||
]
|
||||
self.uidgen.counter = 26*2+10-2
|
||||
actual = [self.uidgen.next_uid() for i in range(8)]
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_rollover_third(self):
|
||||
expected = [
|
||||
"HIE]9",
|
||||
"HIE][",
|
||||
"HIE]]",
|
||||
"HIFAA",
|
||||
"HIFAB",
|
||||
"HIFAC",
|
||||
]
|
||||
self.uidgen.counter = 5*64**2 - 3
|
||||
actual = [self.uidgen.next_uid() for i in range(6)]
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_overflow(self):
|
||||
self.uidgen.counter = 64**3-1
|
||||
self.assertTrue(self.uidgen.next_uid())
|
||||
self.assertRaises(RuntimeError, self.uidgen.next_uid)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -1,70 +0,0 @@
|
||||
"""
|
||||
Tests for protocols/ts6_common
|
||||
"""
|
||||
|
||||
import unittest
|
||||
|
||||
from pylinkirc.protocols import ts6_common
|
||||
|
||||
class TS6UIDGeneratorTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.uidgen = ts6_common.TS6UIDGenerator('123')
|
||||
|
||||
def test_initial_UID(self):
|
||||
expected = [
|
||||
"123AAAAAA",
|
||||
"123AAAAAB",
|
||||
"123AAAAAC",
|
||||
"123AAAAAD",
|
||||
"123AAAAAE",
|
||||
"123AAAAAF",
|
||||
]
|
||||
self.uidgen.counter = 0
|
||||
actual = [self.uidgen.next_uid() for i in range(6)]
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_rollover_first_num(self):
|
||||
expected = [
|
||||
"123AAAAAY",
|
||||
"123AAAAAZ",
|
||||
"123AAAAA0",
|
||||
"123AAAAA1",
|
||||
"123AAAAA2",
|
||||
"123AAAAA3",
|
||||
]
|
||||
self.uidgen.counter = 24
|
||||
actual = [self.uidgen.next_uid() for i in range(6)]
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_rollover_second(self):
|
||||
expected = [
|
||||
"123AAAAA8",
|
||||
"123AAAAA9",
|
||||
"123AAAABA",
|
||||
"123AAAABB",
|
||||
"123AAAABC",
|
||||
"123AAAABD",
|
||||
]
|
||||
self.uidgen.counter = 36 - 2
|
||||
actual = [self.uidgen.next_uid() for i in range(6)]
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_rollover_third(self):
|
||||
expected = [
|
||||
"123AAAE98",
|
||||
"123AAAE99",
|
||||
"123AAAFAA",
|
||||
"123AAAFAB",
|
||||
"123AAAFAC",
|
||||
]
|
||||
self.uidgen.counter = 5*36**2 - 2
|
||||
actual = [self.uidgen.next_uid() for i in range(5)]
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_overflow(self):
|
||||
self.uidgen.counter = 36**6-1
|
||||
self.assertTrue(self.uidgen.next_uid())
|
||||
self.assertRaises(RuntimeError, self.uidgen.next_uid)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
9
utils.py
9
utils.py
@ -20,15 +20,6 @@ from pylinkirc import plugins, protocols
|
||||
from . import conf, structures, world
|
||||
from .log import log
|
||||
|
||||
__all__ = ['PLUGIN_PREFIX', 'PROTOCOL_PREFIX', 'NORMALIZEWHITESPACE_RE',
|
||||
'NotAuthorizedError', 'InvalidArgumentsError', 'ProtocolError',
|
||||
'add_cmd', 'add_hook', 'expand_path', 'split_hostmask',
|
||||
'ServiceBot', 'register_service', 'unregister_service',
|
||||
'wrap_arguments', 'IRCParser', 'strip_irc_formatting',
|
||||
'remove_range', 'get_hostname_type', 'parse_duration', 'match_text',
|
||||
'merge_iterables']
|
||||
|
||||
|
||||
PLUGIN_PREFIX = plugins.__name__ + '.'
|
||||
PROTOCOL_PREFIX = protocols.__name__ + '.'
|
||||
NORMALIZEWHITESPACE_RE = re.compile(r'\s+')
|
||||
|
4
world.py
4
world.py
@ -6,10 +6,6 @@ import threading
|
||||
import time
|
||||
from collections import defaultdict, deque
|
||||
|
||||
__all__ = ['testing', 'hooks', 'networkobjects', 'plugins', 'services',
|
||||
'exttarget_handlers', 'started', 'start_ts', 'shutting_down',
|
||||
'source', 'fallback_hostname', 'daemon']
|
||||
|
||||
# This indicates whether we're running in tests mode. What it actually does
|
||||
# though is control whether IRC connections should be threaded or not.
|
||||
testing = False
|
||||
|
Loading…
x
Reference in New Issue
Block a user