mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-12-12 05:09:45 +01:00
Compare commits
13 Commits
1952dae6f7
...
8fe517f48a
Author | SHA1 | Date | |
---|---|---|---|
|
8fe517f48a | ||
|
45c7615f4a | ||
|
d308329461 | ||
|
aa6bd7257d | ||
|
bdb80b196a | ||
|
0f1011081e | ||
|
e19282a2d3 | ||
|
5baf87ddba | ||
|
0af4af16d3 | ||
|
64ae28c0b8 | ||
|
b8aa5aa33e | ||
|
c23227cdc7 | ||
|
6b72672a1e |
62
.github/workflows/test.yml
vendored
Normal file
62
.github/workflows/test.yml
vendored
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
name: Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.runs-on }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.5", "3.6", "3.7", "3.8", "3.9", "3.10.0-beta.4", "pypy-3.6", "pypy-3.7"]
|
||||||
|
with-opt-deps: [false, true]
|
||||||
|
runs-on: [ubuntu-latest]
|
||||||
|
exclude:
|
||||||
|
# Some of the dependencies don't work on old Python versions
|
||||||
|
- python-version: "3.4"
|
||||||
|
with-opt-deps: true
|
||||||
|
- python-version: "3.5"
|
||||||
|
with-opt-deps: true
|
||||||
|
- python-version: "3.6"
|
||||||
|
with-opt-deps: true
|
||||||
|
- python-version: "pypy-3.6"
|
||||||
|
with-opt-deps: true
|
||||||
|
include:
|
||||||
|
- python-version: "3.4"
|
||||||
|
with-opt-deps: false
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Upgrade pip
|
||||||
|
run: |
|
||||||
|
python3 -m pip install --upgrade pip
|
||||||
|
|
||||||
|
- name: Install optional dependencies
|
||||||
|
if: ${{ matrix.with-opt-deps }}
|
||||||
|
run: |
|
||||||
|
python3 -m pip install --upgrade pip
|
||||||
|
pip3 install -r requirements.txt
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
run: |
|
||||||
|
LIMNORIA_WARN_OLD_PYTHON=0 python3 setup.py install
|
||||||
|
|
||||||
|
- name: Test with unittest
|
||||||
|
run: |
|
||||||
|
supybot-test test -v --plugins-dir=./plugins/ --no-network
|
||||||
|
|
||||||
|
- name: Test with irctest
|
||||||
|
if: "${{ matrix.with-opt-deps && matrix.python-version != 'pypy-3.7' }}"
|
||||||
|
run: |
|
||||||
|
git clone https://github.com/ProgVal/irctest.git
|
||||||
|
cd irctest
|
||||||
|
pip3 install -r requirements.txt
|
||||||
|
make limnoria
|
54
.travis.yml
54
.travis.yml
@ -1,54 +0,0 @@
|
|||||||
language: python
|
|
||||||
sudo: true
|
|
||||||
install:
|
|
||||||
- if [ "$WITH_OPT_DEPS" = "true" ] ; then pip install -vr requirements.txt pytest; fi
|
|
||||||
- git clone https://github.com/ProgVal/irctest.git
|
|
||||||
- echo "y" | pip uninstall limnoria || true
|
|
||||||
# command to run tests, e.g. python setup.py test
|
|
||||||
script:
|
|
||||||
- echo $TRAVIS_PYTHON_VERSION
|
|
||||||
- python setup.py install
|
|
||||||
- supybot-test test -v --plugins-dir=./plugins/ --no-network
|
|
||||||
- if [ "$WITH_OPT_DEPS" = "true" ] -a [[ "$TRAVIS_PYTHON_VERSION" =~ ^3\.[4-9] ]] ; then cd irctest; pytest --controllers irctest.controllers.limnoria; fi
|
|
||||||
notifications:
|
|
||||||
email: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- python: "3.4"
|
|
||||||
env: WITH_OPT_DEPS=false
|
|
||||||
dist: trusty
|
|
||||||
- python: "3.5"
|
|
||||||
env: WITH_OPT_DEPS=false
|
|
||||||
dist: trusty
|
|
||||||
- python: "3.6"
|
|
||||||
env: WITH_OPT_DEPS=false
|
|
||||||
dist: trusty
|
|
||||||
|
|
||||||
- python: "3.7"
|
|
||||||
env: WITH_OPT_DEPS=false
|
|
||||||
dist: xenial
|
|
||||||
- python: "3.7"
|
|
||||||
env: WITH_OPT_DEPS=true
|
|
||||||
dist: xenial
|
|
||||||
- python: "3.8"
|
|
||||||
env: WITH_OPT_DEPS=true
|
|
||||||
dist: xenial
|
|
||||||
- python: "3.9"
|
|
||||||
env: WITH_OPT_DEPS=true
|
|
||||||
dist: xenial
|
|
||||||
- python: "nightly"
|
|
||||||
env: WITH_OPT_DEPS=true
|
|
||||||
dist: xenial
|
|
||||||
|
|
||||||
- python: "pypy3"
|
|
||||||
env: WITH_OPT_DEPS=false
|
|
||||||
dist: trusty
|
|
||||||
|
|
||||||
- python: "nightly"
|
|
||||||
env: WITH_OPT_DEPS=true
|
|
||||||
dist: xenial
|
|
||||||
|
|
||||||
allow_failures:
|
|
||||||
- python: "pypy3"
|
|
||||||
env: WITH_OPT_DEPS=true
|
|
||||||
dist: xenial
|
|
@ -13,7 +13,7 @@ Searches for results on DuckDuckGo.
|
|||||||
|
|
||||||
Example::
|
Example::
|
||||||
|
|
||||||
<+GLolol> %ddg search eiffel tower
|
<+jlu5> %ddg search eiffel tower
|
||||||
<@Atlas> The Eiffel Tower is an iron lattice tower located on the Champ de Mars in Paris. It was named after the engineer Gustave Eiffel, whose company designed and built the tower. - <https://en.wikipedia.org/wiki/Eiffel_Tower>
|
<@Atlas> The Eiffel Tower is an iron lattice tower located on the Champ de Mars in Paris. It was named after the engineer Gustave Eiffel, whose company designed and built the tower. - <https://en.wikipedia.org/wiki/Eiffel_Tower>
|
||||||
|
|
||||||
.. _commands-DDG:
|
.. _commands-DDG:
|
||||||
|
@ -55,7 +55,7 @@ class DDG(callbacks.Plugin):
|
|||||||
|
|
||||||
Example::
|
Example::
|
||||||
|
|
||||||
<+GLolol> %ddg search eiffel tower
|
<+jlu5> %ddg search eiffel tower
|
||||||
<@Atlas> The Eiffel Tower is an iron lattice tower located on the Champ de Mars in Paris. It was named after the engineer Gustave Eiffel, whose company designed and built the tower. - <https://en.wikipedia.org/wiki/Eiffel_Tower>
|
<@Atlas> The Eiffel Tower is an iron lattice tower located on the Champ de Mars in Paris. It was named after the engineer Gustave Eiffel, whose company designed and built the tower. - <https://en.wikipedia.org/wiki/Eiffel_Tower>
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -126,7 +126,7 @@ class Google(callbacks.PluginRegexp):
|
|||||||
'plugin for your searches, like '
|
'plugin for your searches, like '
|
||||||
'<https://github.com/Hoaas/Supybot-plugins/tree/master/DuckDuckGo>, '
|
'<https://github.com/Hoaas/Supybot-plugins/tree/master/DuckDuckGo>, '
|
||||||
'<https://github.com/joulez/GoogleCSE>, or '
|
'<https://github.com/joulez/GoogleCSE>, or '
|
||||||
'<https://github.com/GLolol/SupyPlugins/tree/master/DDG>.')
|
'<https://github.com/jlu5/SupyPlugins/tree/master/DDG>.')
|
||||||
ref = self.registryValue('referer')
|
ref = self.registryValue('referer')
|
||||||
if not ref:
|
if not ref:
|
||||||
ref = 'http://%s/%s' % (dynamic.irc.server,
|
ref = 'http://%s/%s' % (dynamic.irc.server,
|
||||||
|
@ -129,8 +129,13 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler):
|
|||||||
|
|
||||||
def _runCommandFunction(self, irc, msg, command):
|
def _runCommandFunction(self, irc, msg, command):
|
||||||
"""Run a command from message, as if command was sent over IRC."""
|
"""Run a command from message, as if command was sent over IRC."""
|
||||||
tokens = callbacks.tokenize(command,
|
try:
|
||||||
channel=msg.channel, network=irc.network)
|
tokens = callbacks.tokenize(command,
|
||||||
|
channel=msg.channel, network=irc.network)
|
||||||
|
except SyntaxError as e:
|
||||||
|
# Emulate what callbacks.py does
|
||||||
|
self.log.debug('Error return: %s', utils.exnToString(e))
|
||||||
|
irc.error(str(e))
|
||||||
try:
|
try:
|
||||||
self.Proxy(irc.irc, msg, tokens)
|
self.Proxy(irc.irc, msg, tokens)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -85,6 +85,11 @@ class MessageParserTestCase(ChannelPluginTestCase):
|
|||||||
self.assertResponse(' ', '$1')
|
self.assertResponse(' ', '$1')
|
||||||
self.assertNotError('messageparser remove "this( .+)? a(.*)"')
|
self.assertNotError('messageparser remove "this( .+)? a(.*)"')
|
||||||
|
|
||||||
|
def testSyntaxError(self):
|
||||||
|
self.assertNotError(r'messageparser add "test" "echo foo \" bar"')
|
||||||
|
self.feedMsg('test')
|
||||||
|
self.assertResponse(' ', 'Error: No closing quotation')
|
||||||
|
|
||||||
def testShow(self):
|
def testShow(self):
|
||||||
self.assertNotError('messageparser add "stuff" "echo i saw some stuff"')
|
self.assertNotError('messageparser add "stuff" "echo i saw some stuff"')
|
||||||
self.assertRegexp('messageparser show "nostuff"', 'there is no such regexp trigger')
|
self.assertRegexp('messageparser show "nostuff"', 'there is no such regexp trigger')
|
||||||
|
@ -37,7 +37,7 @@ Examples
|
|||||||
< Mikaela> @load PluginDownloader
|
< Mikaela> @load PluginDownloader
|
||||||
< Limnoria> Ok.
|
< Limnoria> Ok.
|
||||||
< Mikaela> @plugindownloader repolist
|
< Mikaela> @plugindownloader repolist
|
||||||
< Limnoria> Antibody, GLolol, Hoaas, Iota, ProgVal, SpiderDave, boombot, code4lib, code4lib-edsu, code4lib-snapshot, doorbot, frumious, jonimoose, mailed-notifier, mtughan-weather, nanotube-bitcoin, nyuszika7h, nyuszika7h-old, pingdom, quantumlemur, resistivecorpse, scrum, skgsergio, stepnem
|
< Limnoria> Antibody, jlu5, Hoaas, Iota, ProgVal, SpiderDave, boombot, code4lib, code4lib-edsu, code4lib-snapshot, doorbot, frumious, jonimoose, mailed-notifier, mtughan-weather, nanotube-bitcoin, nyuszika7h, nyuszika7h-old, pingdom, quantumlemur, resistivecorpse, scrum, skgsergio, stepnem
|
||||||
< Mikaela> @plugindownloader repolist ProgVal
|
< Mikaela> @plugindownloader repolist ProgVal
|
||||||
< Limnoria> AttackProtector, AutoTrans, Biography, Brainfuck, ChannelStatus, Cleverbot, Coffee, Coinpan, Debian, ERepublik, Eureka, Fortune, GUI, GitHub, Glob2Chan, GoodFrench, I18nPlaceholder, IMDb, IgnoreNonVoice, Iwant, Kickme, LimnoriaChan, LinkRelay, ListEmpty, Listener, Markovgen, MegaHAL, MilleBornes, NoLatin1, NoisyKarma, OEIS, PPP, PingTime, Pinglist, RateLimit, Rbls, Redmine, Scheme, Seeks, (1 more message)
|
< Limnoria> AttackProtector, AutoTrans, Biography, Brainfuck, ChannelStatus, Cleverbot, Coffee, Coinpan, Debian, ERepublik, Eureka, Fortune, GUI, GitHub, Glob2Chan, GoodFrench, I18nPlaceholder, IMDb, IgnoreNonVoice, Iwant, Kickme, LimnoriaChan, LinkRelay, ListEmpty, Listener, Markovgen, MegaHAL, MilleBornes, NoLatin1, NoisyKarma, OEIS, PPP, PingTime, Pinglist, RateLimit, Rbls, Redmine, Scheme, Seeks, (1 more message)
|
||||||
< Mikaela> more
|
< Mikaela> more
|
||||||
|
@ -348,7 +348,7 @@ class PluginDownloader(callbacks.Plugin):
|
|||||||
< Mikaela> @load PluginDownloader
|
< Mikaela> @load PluginDownloader
|
||||||
< Limnoria> Ok.
|
< Limnoria> Ok.
|
||||||
< Mikaela> @plugindownloader repolist
|
< Mikaela> @plugindownloader repolist
|
||||||
< Limnoria> Antibody, GLolol, Hoaas, Iota, ProgVal, SpiderDave, boombot, code4lib, code4lib-edsu, code4lib-snapshot, doorbot, frumious, jonimoose, mailed-notifier, mtughan-weather, nanotube-bitcoin, nyuszika7h, nyuszika7h-old, pingdom, quantumlemur, resistivecorpse, scrum, skgsergio, stepnem
|
< Limnoria> Antibody, jlu5, Hoaas, Iota, ProgVal, SpiderDave, boombot, code4lib, code4lib-edsu, code4lib-snapshot, doorbot, frumious, jonimoose, mailed-notifier, mtughan-weather, nanotube-bitcoin, nyuszika7h, nyuszika7h-old, pingdom, quantumlemur, resistivecorpse, scrum, skgsergio, stepnem
|
||||||
< Mikaela> @plugindownloader repolist ProgVal
|
< Mikaela> @plugindownloader repolist ProgVal
|
||||||
< Limnoria> AttackProtector, AutoTrans, Biography, Brainfuck, ChannelStatus, Cleverbot, Coffee, Coinpan, Debian, ERepublik, Eureka, Fortune, GUI, GitHub, Glob2Chan, GoodFrench, I18nPlaceholder, IMDb, IgnoreNonVoice, Iwant, Kickme, LimnoriaChan, LinkRelay, ListEmpty, Listener, Markovgen, MegaHAL, MilleBornes, NoLatin1, NoisyKarma, OEIS, PPP, PingTime, Pinglist, RateLimit, Rbls, Redmine, Scheme, Seeks, (1 more message)
|
< Limnoria> AttackProtector, AutoTrans, Biography, Brainfuck, ChannelStatus, Cleverbot, Coffee, Coinpan, Debian, ERepublik, Eureka, Fortune, GUI, GitHub, Glob2Chan, GoodFrench, I18nPlaceholder, IMDb, IgnoreNonVoice, Iwant, Kickme, LimnoriaChan, LinkRelay, ListEmpty, Listener, Markovgen, MegaHAL, MilleBornes, NoLatin1, NoisyKarma, OEIS, PPP, PingTime, Pinglist, RateLimit, Rbls, Redmine, Scheme, Seeks, (1 more message)
|
||||||
< Mikaela> more
|
< Mikaela> more
|
||||||
|
@ -25,7 +25,7 @@ Creates a poll that can be voted on in this way::
|
|||||||
And results::
|
And results::
|
||||||
|
|
||||||
<admin> @poll results
|
<admin> @poll results
|
||||||
<bot> 2 votes for No, 1 vote for Yes, and 0 votes for Maybe",
|
<bot> 2 votes for No, 1 vote for Yes, and 0 votes for Maybe
|
||||||
|
|
||||||
Longer answers are possible, and voters only need to use the first
|
Longer answers are possible, and voters only need to use the first
|
||||||
word of each answer to vote. For example, this creates a poll that
|
word of each answer to vote. For example, this creates a poll that
|
||||||
|
@ -64,7 +64,7 @@ class Poll_(callbacks.Plugin):
|
|||||||
And results::
|
And results::
|
||||||
|
|
||||||
<admin> @poll results
|
<admin> @poll results
|
||||||
<bot> 2 votes for No, 1 vote for Yes, and 0 votes for Maybe",
|
<bot> 2 votes for No, 1 vote for Yes, and 0 votes for Maybe
|
||||||
|
|
||||||
Longer answers are possible, and voters only need to use the first
|
Longer answers are possible, and voters only need to use the first
|
||||||
word of each answer to vote. For example, this creates a poll that
|
word of each answer to vote. For example, this creates a poll that
|
||||||
|
@ -102,6 +102,7 @@ class RSSTestCase(ChannelPluginTestCase):
|
|||||||
|
|
||||||
@mock_urllib
|
@mock_urllib
|
||||||
def testRemoveAliasedFeed(self, mock):
|
def testRemoveAliasedFeed(self, mock):
|
||||||
|
mock._data = xkcd_new
|
||||||
try:
|
try:
|
||||||
self.assertNotError('rss announce add http://xkcd.com/rss.xml')
|
self.assertNotError('rss announce add http://xkcd.com/rss.xml')
|
||||||
self.assertNotError('rss add xkcd http://xkcd.com/rss.xml')
|
self.assertNotError('rss add xkcd http://xkcd.com/rss.xml')
|
||||||
|
@ -16,9 +16,9 @@ After enabling SedRegex, typing a regex in the form
|
|||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
20:24 <~GL> helli world
|
20:24 <jlu5> helli world
|
||||||
20:24 <~GL> s/i/o/
|
20:24 <jlu5> s/i/o/
|
||||||
20:24 <@Lily> GL meant to say: hello world
|
20:24 <Limnoria> jlu5 meant to say: hello world
|
||||||
|
|
||||||
You can also do ``othernick: s/text/replacement/`` to only replace
|
You can also do ``othernick: s/text/replacement/`` to only replace
|
||||||
messages from a certain user. Supybot ignores are respected by the plugin,
|
messages from a certain user. Supybot ignores are respected by the plugin,
|
||||||
|
@ -69,9 +69,9 @@ class SedRegex(callbacks.PluginRegexp):
|
|||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
20:24 <~GL> helli world
|
20:24 <jlu5> helli world
|
||||||
20:24 <~GL> s/i/o/
|
20:24 <jlu5> s/i/o/
|
||||||
20:24 <@Lily> GL meant to say: hello world
|
20:24 <Limnoria> jlu5 meant to say: hello world
|
||||||
|
|
||||||
You can also do ``othernick: s/text/replacement/`` to only replace
|
You can also do ``othernick: s/text/replacement/`` to only replace
|
||||||
messages from a certain user. Supybot ignores are respected by the plugin,
|
messages from a certain user. Supybot ignores are respected by the plugin,
|
||||||
|
@ -146,6 +146,9 @@ class User(callbacks.Plugin):
|
|||||||
user.setPassword(password)
|
user.setPassword(password)
|
||||||
if addHostmask:
|
if addHostmask:
|
||||||
user.addHostmask(msg.prefix)
|
user.addHostmask(msg.prefix)
|
||||||
|
account = msg.server_tags.get('account')
|
||||||
|
if account:
|
||||||
|
user.addNick(irc.network, account)
|
||||||
ircdb.users.setUser(user)
|
ircdb.users.setUser(user)
|
||||||
irc.replySuccess()
|
irc.replySuccess()
|
||||||
register = wrap(register, ['private', 'something', 'something'])
|
register = wrap(register, ['private', 'something', 'something'])
|
||||||
|
11
setup.py
11
setup.py
@ -82,10 +82,19 @@ if version:
|
|||||||
fd.close()
|
fd.close()
|
||||||
|
|
||||||
if sys.version_info < (3, 4, 0):
|
if sys.version_info < (3, 4, 0):
|
||||||
sys.stderr.write("Limnoria requires Python 3.4 or newer.")
|
sys.stderr.write("Limnoria requires Python 3.6 or newer.")
|
||||||
sys.stderr.write(os.linesep)
|
sys.stderr.write(os.linesep)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
|
if sys.version_info < (3, 6, 0) \
|
||||||
|
and os.environ.get('LIMNORIA_WARN_OLD_PYTHON') != '0':
|
||||||
|
sys.stderr.write('====================================================\n')
|
||||||
|
sys.stderr.write('Limnoria support for Python versions older than 3.6\n')
|
||||||
|
sys.stderr.write('is deprecated and may be removed in the near future.\n')
|
||||||
|
sys.stderr.write('You should upgrade ASAP.\n')
|
||||||
|
sys.stderr.write('Install will continue in 60s.\n')
|
||||||
|
sys.stderr.write('====================================================\n')
|
||||||
|
time.sleep(60)
|
||||||
|
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
|
@ -114,6 +114,13 @@ class SocketDriver(drivers.IrcDriver, drivers.ServersMixin):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
self.connected = False
|
self.connected = False
|
||||||
|
if self.irc is None:
|
||||||
|
# This driver is dead already, but we're still running because
|
||||||
|
# of select() running in an other driver's thread that started
|
||||||
|
# before this one died and stil holding a reference to this
|
||||||
|
# instance.
|
||||||
|
# Just return, and we should never be called again.
|
||||||
|
return
|
||||||
self.scheduleReconnect()
|
self.scheduleReconnect()
|
||||||
else:
|
else:
|
||||||
log.debug('Got EAGAIN, current count: %s.', self.eagains)
|
log.debug('Got EAGAIN, current count: %s.', self.eagains)
|
||||||
@ -208,6 +215,8 @@ class SocketDriver(drivers.IrcDriver, drivers.ServersMixin):
|
|||||||
|
|
||||||
msg = drivers.parseMsg(line)
|
msg = drivers.parseMsg(line)
|
||||||
if msg is not None and self.irc is not None:
|
if msg is not None and self.irc is not None:
|
||||||
|
# self.irc may be None if this driver is already dead,
|
||||||
|
# see comment in _handleSocketError
|
||||||
self.irc.feedMsg(msg)
|
self.irc.feedMsg(msg)
|
||||||
except socket.timeout:
|
except socket.timeout:
|
||||||
pass
|
pass
|
||||||
|
@ -389,15 +389,15 @@ class ChannelState(utils.python.Object):
|
|||||||
"""Returns whether the given nick is an halfop, or an op."""
|
"""Returns whether the given nick is an halfop, or an op."""
|
||||||
return nick in self.halfops or nick in self.ops
|
return nick in self.halfops or nick in self.ops
|
||||||
|
|
||||||
def addUser(self, user):
|
def addUser(self, user, prefix_chars='@%+&~!'):
|
||||||
"Adds a given user to the ChannelState. Power prefixes are handled."
|
"Adds a given user to the ChannelState. Power prefixes are handled."
|
||||||
nick = user.lstrip('@%+&~!')
|
nick = user.lstrip(prefix_chars)
|
||||||
if not nick:
|
if not nick:
|
||||||
return
|
return
|
||||||
# & is used to denote protected users in UnrealIRCd
|
# & is used to denote protected users in UnrealIRCd
|
||||||
# ~ is used to denote channel owner in UnrealIRCd
|
# ~ is used to denote channel owner in UnrealIRCd
|
||||||
# ! is used to denote protected users in UltimateIRCd
|
# ! is used to denote protected users in UltimateIRCd
|
||||||
while user and user[0] in '@%+&~!':
|
while user and user[0] in prefix_chars:
|
||||||
(marker, user) = (user[0], user[1:])
|
(marker, user) = (user[0], user[1:])
|
||||||
assert user, 'Looks like my caller is passing chars, not nicks.'
|
assert user, 'Looks like my caller is passing chars, not nicks.'
|
||||||
if marker in '@&~!':
|
if marker in '@&~!':
|
||||||
@ -963,13 +963,27 @@ class IrcState(IrcCommandDispatcher, log.Firewalled):
|
|||||||
if channel not in self.channels:
|
if channel not in self.channels:
|
||||||
self.channels[channel] = ChannelState()
|
self.channels[channel] = ChannelState()
|
||||||
c = self.channels[channel]
|
c = self.channels[channel]
|
||||||
|
|
||||||
|
# Set of prefixes servers may append before a NAMES reply when
|
||||||
|
# the user is op/halfop/voice/...
|
||||||
|
# https://datatracker.ietf.org/doc/html/draft-hardy-irc-isupport-00#section-4.15
|
||||||
|
prefix = self.supported.get('PREFIX')
|
||||||
|
if prefix is None:
|
||||||
|
prefix_chars = '@%+&~!' # see the comments in addUser
|
||||||
|
else:
|
||||||
|
prefix_chars = ''.join(prefix.values())
|
||||||
|
|
||||||
for item in items.split():
|
for item in items.split():
|
||||||
if ircutils.isUserHostmask(item):
|
stripped_item = item.lstrip(prefix_chars)
|
||||||
name = ircutils.nickFromHostmask(item)
|
item_prefix = item[0:-len(stripped_item)]
|
||||||
self.nicksToHostmasks[name] = name
|
if ircutils.isUserHostmask(stripped_item):
|
||||||
|
nick = ircutils.nickFromHostmask(stripped_item)
|
||||||
|
self.nicksToHostmasks[nick] = stripped_item
|
||||||
|
name = item_prefix + nick
|
||||||
else:
|
else:
|
||||||
name = item
|
name = item
|
||||||
c.addUser(name)
|
c.addUser(name, prefix_chars=prefix_chars)
|
||||||
|
|
||||||
if type == '@':
|
if type == '@':
|
||||||
c.modes['s'] = None
|
c.modes['s'] = None
|
||||||
|
|
||||||
|
@ -60,7 +60,11 @@ def debug(s, *args):
|
|||||||
"""Prints a debug string. Most likely replaced by our logging debug."""
|
"""Prints a debug string. Most likely replaced by our logging debug."""
|
||||||
print('***', s % args)
|
print('***', s % args)
|
||||||
|
|
||||||
userHostmaskRe = re.compile(r'^\S+!\S+@\S+$')
|
def warning(s, *args):
|
||||||
|
"""Prints a debug string. Most likely replaced by our logging debug."""
|
||||||
|
print('###', s % args)
|
||||||
|
|
||||||
|
userHostmaskRe = re.compile(r'^(?P<nick>\S+?)!(?P<user>\S+)@(?P<host>\S+?)$')
|
||||||
def isUserHostmask(s):
|
def isUserHostmask(s):
|
||||||
"""Returns whether or not the string s is a valid User hostmask."""
|
"""Returns whether or not the string s is a valid User hostmask."""
|
||||||
return userHostmaskRe.match(s) is not None
|
return userHostmaskRe.match(s) is not None
|
||||||
@ -91,9 +95,17 @@ def hostFromHostmask(hostmask):
|
|||||||
def splitHostmask(hostmask):
|
def splitHostmask(hostmask):
|
||||||
"""hostmask => (nick, user, host)
|
"""hostmask => (nick, user, host)
|
||||||
Returns the nick, user, host of a user hostmask."""
|
Returns the nick, user, host of a user hostmask."""
|
||||||
assert isUserHostmask(hostmask)
|
m = userHostmaskRe.match(hostmask)
|
||||||
nick, rest = hostmask.rsplit('!', 1)
|
assert m is not None, hostmask
|
||||||
user, host = rest.rsplit('@', 1)
|
nick = m.group("nick")
|
||||||
|
user = m.group("user")
|
||||||
|
host = m.group("host")
|
||||||
|
if set("!@") & set(nick+user+host):
|
||||||
|
# There should never be either of these characters in the part.
|
||||||
|
# As far as I know it never happens in practice aside from networks
|
||||||
|
# broken by design.
|
||||||
|
warning("Invalid hostmask format: %s", hostmask)
|
||||||
|
# TODO: error if strictRfc is True
|
||||||
return (minisix.intern(nick), minisix.intern(user), minisix.intern(host))
|
return (minisix.intern(nick), minisix.intern(user), minisix.intern(host))
|
||||||
|
|
||||||
def joinHostmask(nick, ident, host):
|
def joinHostmask(nick, ident, host):
|
||||||
|
@ -329,6 +329,7 @@ atexit.register(logging.shutdown)
|
|||||||
|
|
||||||
# ircutils will work without this, but it's useful.
|
# ircutils will work without this, but it's useful.
|
||||||
ircutils.debug = debug
|
ircutils.debug = debug
|
||||||
|
ircutils.warning = warning
|
||||||
|
|
||||||
def getPluginLogger(name):
|
def getPluginLogger(name):
|
||||||
if not conf.supybot.log.plugins.individualLogfiles():
|
if not conf.supybot.log.plugins.individualLogfiles():
|
||||||
|
@ -529,6 +529,42 @@ class IrcStateTestCase(SupyTestCase):
|
|||||||
st = irclib.IrcState()
|
st = irclib.IrcState()
|
||||||
self.assert_(st.addMsg(self.irc, ircmsgs.IrcMsg('MODE foo +i')) or 1)
|
self.assert_(st.addMsg(self.irc, ircmsgs.IrcMsg('MODE foo +i')) or 1)
|
||||||
|
|
||||||
|
def testNamreply(self):
|
||||||
|
"""RPL_NAMREPLY / reply to NAMES"""
|
||||||
|
|
||||||
|
# Just nicks (à la RFC 1459 + some common prefix chars)
|
||||||
|
st = irclib.IrcState()
|
||||||
|
st.addMsg(self.irc, ircmsgs.IrcMsg(command='353',
|
||||||
|
args=('*', '=', '#chan', 'nick1 @nick2 ~@nick3')))
|
||||||
|
chan_st = st.channels['#chan']
|
||||||
|
self.assertEqual(chan_st.users, ircutils.IrcSet(['nick1', 'nick2', 'nick3']))
|
||||||
|
self.assertEqual(chan_st.ops, ircutils.IrcSet(['nick2', 'nick3']))
|
||||||
|
self.assertEqual(st.nicksToHostmasks, ircutils.IrcDict({}))
|
||||||
|
|
||||||
|
# with userhost-in-names
|
||||||
|
st = irclib.IrcState()
|
||||||
|
st.addMsg(self.irc, ircmsgs.IrcMsg(command='353',
|
||||||
|
args=('*', '=', '#chan', 'nick1!u1@h1 @nick2!u2@h2 ~@nick3!u3@h3')))
|
||||||
|
chan_st = st.channels['#chan']
|
||||||
|
self.assertEqual(chan_st.users, ircutils.IrcSet(['nick1', 'nick2', 'nick3']))
|
||||||
|
self.assertEqual(chan_st.ops, ircutils.IrcSet(['nick2', 'nick3']))
|
||||||
|
self.assertEqual(st.nicksToHostmasks['nick1'], 'nick1!u1@h1')
|
||||||
|
self.assertEqual(st.nicksToHostmasks['nick2'], 'nick2!u2@h2')
|
||||||
|
self.assertEqual(st.nicksToHostmasks['nick3'], 'nick3!u3@h3')
|
||||||
|
|
||||||
|
# Prefixed with chars not in ISUPPORT PREFIX
|
||||||
|
st = irclib.IrcState()
|
||||||
|
st.addMsg(self.irc, ircmsgs.IrcMsg(command='005',
|
||||||
|
args=('*', 'PREFIX=(ov)@+', 'are supported')))
|
||||||
|
st.addMsg(self.irc, ircmsgs.IrcMsg(command='353',
|
||||||
|
args=('*', '=', '#chan', 'nick1!u1@h1 @nick2!u2@h2 ~@nick3!u3@h3')))
|
||||||
|
chan_st = st.channels['#chan']
|
||||||
|
self.assertEqual(chan_st.users, ircutils.IrcSet(['nick1', 'nick2', '~@nick3']))
|
||||||
|
self.assertEqual(chan_st.ops, ircutils.IrcSet(['nick2']))
|
||||||
|
self.assertEqual(st.nicksToHostmasks['nick1'], 'nick1!u1@h1')
|
||||||
|
self.assertEqual(st.nicksToHostmasks['nick2'], 'nick2!u2@h2')
|
||||||
|
self.assertEqual(st.nicksToHostmasks['~@nick3'], '~@nick3!u3@h3')
|
||||||
|
|
||||||
|
|
||||||
class IrcCapsTestCase(SupyTestCase, CapNegMixin):
|
class IrcCapsTestCase(SupyTestCase, CapNegMixin):
|
||||||
def testReqLineLength(self):
|
def testReqLineLength(self):
|
||||||
|
@ -113,6 +113,24 @@ class FunctionsTestCase(SupyTestCase):
|
|||||||
self.assertFalse(ircutils.isUserHostmask('@'))
|
self.assertFalse(ircutils.isUserHostmask('@'))
|
||||||
self.assertFalse(ircutils.isUserHostmask('!bar@baz'))
|
self.assertFalse(ircutils.isUserHostmask('!bar@baz'))
|
||||||
|
|
||||||
|
def testSplitHostmask(self):
|
||||||
|
# This is the only valid case:
|
||||||
|
self.assertEqual(ircutils.splitHostmask('foo!bar@baz'),
|
||||||
|
('foo', 'bar', 'baz'))
|
||||||
|
|
||||||
|
# This ones are technically allowed by RFC1459, but never happens in
|
||||||
|
# practice:
|
||||||
|
self.assertEqual(ircutils.splitHostmask('foo!bar!qux@quux'),
|
||||||
|
('foo', 'bar!qux', 'quux'))
|
||||||
|
self.assertEqual(ircutils.splitHostmask('foo!bar@baz@quux'),
|
||||||
|
('foo', 'bar@baz', 'quux'))
|
||||||
|
self.assertEqual(ircutils.splitHostmask('foo!bar@baz!qux@quux'),
|
||||||
|
('foo', 'bar@baz!qux', 'quux'))
|
||||||
|
|
||||||
|
# And this one in garbage, let's just make sure we don't crash:
|
||||||
|
self.assertEqual(ircutils.splitHostmask('foo!bar@baz!qux'),
|
||||||
|
('foo', 'bar', 'baz!qux'))
|
||||||
|
|
||||||
def testIsChannel(self):
|
def testIsChannel(self):
|
||||||
self.assertTrue(ircutils.isChannel('#'))
|
self.assertTrue(ircutils.isChannel('#'))
|
||||||
self.assertTrue(ircutils.isChannel('&'))
|
self.assertTrue(ircutils.isChannel('&'))
|
||||||
|
Loading…
Reference in New Issue
Block a user