mirror of
https://github.com/Mikaela/Limnoria.git
synced 2025-08-17 18:37:22 +02:00
Compare commits
No commits in common. "9a4dca80544cd8a64f963e775e0207b4fd9eb61d" and "d435442b39f167509e64b21a9cd1cf3f71e33033" have entirely different histories.
9a4dca8054
...
d435442b39
8
.github/workflows/test.yml
vendored
8
.github/workflows/test.yml
vendored
@ -15,11 +15,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- python-version: "3.13.0-alpha.6"
|
- python-version: "3.12.0-alpha.7"
|
||||||
with-opt-deps: false # https://github.com/pyca/cryptography/issues/10806
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
|
|
||||||
- python-version: "3.12.0"
|
|
||||||
with-opt-deps: true
|
with-opt-deps: true
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
@ -71,7 +67,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upgrade pip
|
- name: Upgrade pip
|
||||||
run: |
|
run: |
|
||||||
python3 -m pip install --upgrade pip setuptools
|
python3 -m pip install --upgrade pip
|
||||||
|
|
||||||
- name: Install optional dependencies
|
- name: Install optional dependencies
|
||||||
if: ${{ matrix.with-opt-deps }}
|
if: ${{ matrix.with-opt-deps }}
|
||||||
|
@ -15,10 +15,14 @@ Last rule: you shouldn't add a mandatory dependency. Limnoria does not
|
|||||||
come with any (besides Python), so please try to keep all dependencies
|
come with any (besides Python), so please try to keep all dependencies
|
||||||
optional.
|
optional.
|
||||||
|
|
||||||
[Style Guidelines]:https://docs.limnoria.net/develop/style.html
|
[Style Guidelines]:https://limnoria.readthedocs.io/en/latest/develop/style.html
|
||||||
|
|
||||||
## Sending patches
|
## Sending patches
|
||||||
|
|
||||||
|
When you send a pull request, **send it to the testing branch**.
|
||||||
|
It will be merged to master when it's considered to be stable enough to be
|
||||||
|
supported.
|
||||||
|
|
||||||
Don't fear that you spam Limnoria by sending many pull requests. According
|
Don't fear that you spam Limnoria by sending many pull requests. According
|
||||||
to @ProgVal, it's easier for them to accept pull requests than to
|
to @ProgVal, it's easier for them to accept pull requests than to
|
||||||
cherry-pick everything manually.
|
cherry-pick everything manually.
|
||||||
@ -28,6 +32,6 @@ is very appreciated.
|
|||||||
|
|
||||||
See also [Contributing to Limnoria] at [Limnoria documentation].
|
See also [Contributing to Limnoria] at [Limnoria documentation].
|
||||||
|
|
||||||
[Contributing to Limnoria]:https://docs.limnoria.net/contribute/index.html
|
[Contributing to Limnoria]:https://limnoria.readthedocs.io/en/latest/contribute/index.html
|
||||||
|
|
||||||
[Limnoria documentation]:https://docs.limnoria.net/
|
[Limnoria documentation]:https://limnoria.readthedocs.io/
|
||||||
|
@ -39,7 +39,7 @@ class DDGTestCase(PluginTestCase):
|
|||||||
|
|
||||||
def testSearch(self):
|
def testSearch(self):
|
||||||
self.assertRegexp(
|
self.assertRegexp(
|
||||||
'ddg search wikipedia', r'Wikipedia.*? - .*?https?\:\/\/')
|
'ddg search wikipedia', 'Wikipedia.*? - .*?https?\:\/\/')
|
||||||
self.assertRegexp(
|
self.assertRegexp(
|
||||||
'ddg search en.wikipedia.org',
|
'ddg search en.wikipedia.org',
|
||||||
'Wikipedia, the free encyclopedia\x02 - '
|
'Wikipedia, the free encyclopedia\x02 - '
|
||||||
@ -47,6 +47,6 @@ class DDGTestCase(PluginTestCase):
|
|||||||
with conf.supybot.plugins.DDG.region.context('fr-fr'):
|
with conf.supybot.plugins.DDG.region.context('fr-fr'):
|
||||||
self.assertRegexp(
|
self.assertRegexp(
|
||||||
'ddg search wikipedia',
|
'ddg search wikipedia',
|
||||||
r'Wikipédia, l\'encyclopédie libre - .*?https?\:\/\/')
|
'Wikipédia, l\'encyclopédie libre - .*?https?\:\/\/')
|
||||||
|
|
||||||
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:
|
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:
|
||||||
|
@ -194,7 +194,7 @@ class Connection:
|
|||||||
if code != 151 or code is None:
|
if code != 151 or code is None:
|
||||||
break
|
break
|
||||||
|
|
||||||
resultword, resultdb = re.search(r'^"(.+)" (\S+)', text).groups()
|
resultword, resultdb = re.search('^"(.+)" (\S+)', text).groups()
|
||||||
defstr = self.get100block()
|
defstr = self.get100block()
|
||||||
retval.append(Definition(self, self.getdbobj(resultdb),
|
retval.append(Definition(self, self.getdbobj(resultdb),
|
||||||
resultword, defstr))
|
resultword, defstr))
|
||||||
|
@ -33,11 +33,11 @@ import datetime
|
|||||||
|
|
||||||
# Credits for the regexp and function: https://stackoverflow.com/a/2765366/539465
|
# Credits for the regexp and function: https://stackoverflow.com/a/2765366/539465
|
||||||
_XSD_DURATION_RE = re.compile(
|
_XSD_DURATION_RE = re.compile(
|
||||||
r"(?P<sign>-?)P"
|
"(?P<sign>-?)P"
|
||||||
r"(?:(?P<years>\d+)Y)?"
|
"(?:(?P<years>\d+)Y)?"
|
||||||
r"(?:(?P<months>\d+)M)?"
|
"(?:(?P<months>\d+)M)?"
|
||||||
r"(?:(?P<days>\d+)D)?"
|
"(?:(?P<days>\d+)D)?"
|
||||||
r"(?:T(?:(?P<hours>\d+)H)?(?:(?P<minutes>\d+)M)?(?:(?P<seconds>\d+)S)?)?"
|
"(?:T(?:(?P<hours>\d+)H)?(?:(?P<minutes>\d+)M)?(?:(?P<seconds>\d+)S)?)?"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
import time
|
import time
|
||||||
import socket
|
import socket
|
||||||
|
import telnetlib
|
||||||
|
|
||||||
import supybot.conf as conf
|
import supybot.conf as conf
|
||||||
import supybot.utils as utils
|
import supybot.utils as utils
|
||||||
@ -157,14 +158,14 @@ class Internet(callbacks.Plugin):
|
|||||||
if not status:
|
if not status:
|
||||||
status = 'unknown'
|
status = 'unknown'
|
||||||
try:
|
try:
|
||||||
sock = socket.create_connection(('whois.iana.org', 43))
|
t = telnetlib.Telnet('whois.iana.org', 43)
|
||||||
except socket.error as e:
|
except socket.error as e:
|
||||||
irc.error(str(e))
|
irc.error(str(e))
|
||||||
return
|
return
|
||||||
sock.sendall(b'registrar ')
|
t.write(b'registrar ')
|
||||||
sock.sendall(registrar.split('(')[0].strip().encode('ascii'))
|
t.write(registrar.split('(')[0].strip().encode('ascii'))
|
||||||
sock.sendall(b'\n')
|
t.write(b'\n')
|
||||||
s = sock.recv(100000)
|
s = t.read_all()
|
||||||
url = ''
|
url = ''
|
||||||
for line in s.splitlines():
|
for line in s.splitlines():
|
||||||
line = line.decode('ascii').strip()
|
line = line.decode('ascii').strip()
|
||||||
|
@ -40,6 +40,7 @@ class InternetTestCase(PluginTestCase):
|
|||||||
'Host not found.')
|
'Host not found.')
|
||||||
|
|
||||||
def testWhois(self):
|
def testWhois(self):
|
||||||
|
self.assertNotError('internet whois ohio-state.edu')
|
||||||
self.assertNotError('internet whois microsoft.com')
|
self.assertNotError('internet whois microsoft.com')
|
||||||
self.assertNotError('internet whois inria.fr')
|
self.assertNotError('internet whois inria.fr')
|
||||||
self.assertNotError('internet whois slime.com.au')
|
self.assertNotError('internet whois slime.com.au')
|
||||||
|
@ -849,7 +849,7 @@ class UnitGroup:
|
|||||||
|
|
||||||
def updateCurrentUnit(self, text, cursorPos):
|
def updateCurrentUnit(self, text, cursorPos):
|
||||||
"Set current unit number"
|
"Set current unit number"
|
||||||
self.currentNum = len(re.findall(r'[\*/]', text[:cursorPos]))
|
self.currentNum = len(re.findall('[\*/]', text[:cursorPos]))
|
||||||
|
|
||||||
def currentUnit(self):
|
def currentUnit(self):
|
||||||
"Return current unit if its a full match, o/w None"
|
"Return current unit if its a full match, o/w None"
|
||||||
@ -925,7 +925,7 @@ class UnitGroup:
|
|||||||
def parseGroup(self, text):
|
def parseGroup(self, text):
|
||||||
"Return list of units from text string"
|
"Return list of units from text string"
|
||||||
unitList = []
|
unitList = []
|
||||||
parts = [part.strip() for part in re.split(r'([\*/])', text)]
|
parts = [part.strip() for part in re.split('([\*/])', text)]
|
||||||
numerator = 1
|
numerator = 1
|
||||||
while parts:
|
while parts:
|
||||||
unit = self.parseUnit(parts.pop(0))
|
unit = self.parseUnit(parts.pop(0))
|
||||||
@ -1180,7 +1180,7 @@ class Unit:
|
|||||||
self.equiv = unitList[0].strip()
|
self.equiv = unitList[0].strip()
|
||||||
if self.equiv[0] == '[': # used only for non-linear units
|
if self.equiv[0] == '[': # used only for non-linear units
|
||||||
try:
|
try:
|
||||||
self.equiv, self.fromEqn = re.match(r'\[(.*?)\](.*)', \
|
self.equiv, self.fromEqn = re.match('\[(.*?)\](.*)', \
|
||||||
self.equiv).groups()
|
self.equiv).groups()
|
||||||
if ';' in self.fromEqn:
|
if ';' in self.fromEqn:
|
||||||
self.fromEqn, self.toEqn = self.fromEqn.split(';', 1)
|
self.fromEqn, self.toEqn = self.fromEqn.split(';', 1)
|
||||||
@ -1190,7 +1190,7 @@ class Unit:
|
|||||||
raise UnitDataError('Bad equation for "%s"' % self.name)
|
raise UnitDataError('Bad equation for "%s"' % self.name)
|
||||||
else: # split factor and equiv unit for linear
|
else: # split factor and equiv unit for linear
|
||||||
parts = self.equiv.split(None, 1)
|
parts = self.equiv.split(None, 1)
|
||||||
if len(parts) > 1 and re.search(r'[^\d\.eE\+\-\*/]', parts[0]) \
|
if len(parts) > 1 and re.search('[^\d\.eE\+\-\*/]', parts[0]) \
|
||||||
== None: # only allowed digits and operators
|
== None: # only allowed digits and operators
|
||||||
try:
|
try:
|
||||||
self.factor = float(eval(parts[0]))
|
self.factor = float(eval(parts[0]))
|
||||||
|
@ -342,31 +342,21 @@ class Misc(callbacks.Plugin):
|
|||||||
Returns the version of the current bot.
|
Returns the version of the current bot.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
versions = []
|
newestUrl = 'https://api.github.com/repos/progval/Limnoria/' + \
|
||||||
|
'commits/%s'
|
||||||
# fetch from PyPI
|
versions = {}
|
||||||
data = json.loads(utils.web.getUrl(
|
for branch in ('master', 'testing'):
|
||||||
'https://pypi.org/pypi/limnoria/json'
|
data = json.loads(utils.web.getUrl(newestUrl % branch)
|
||||||
).decode('utf8'))
|
.decode('utf8'))
|
||||||
release_version = data['info']['version']
|
version = data['commit']['committer']['date']
|
||||||
# zero-left-pad months and days
|
# Strip the last 'Z':
|
||||||
release_version = re.sub(
|
version = version.rsplit('T', 1)[0].replace('-', '.')
|
||||||
r'\.([0-9])\b', lambda m: '.0' + m.group(1), release_version
|
if minisix.PY2 and isinstance(version, unicode):
|
||||||
)
|
version = version.encode('utf8')
|
||||||
|
versions[branch] = version
|
||||||
# fetch from Git
|
newest = _('The newest versions available online are %s.') % \
|
||||||
data = json.loads(utils.web.getUrl(
|
', '.join([_('%s (in %s)') % (y,x)
|
||||||
'https://api.github.com/repos/progval/Limnoria/'
|
for x,y in versions.items()])
|
||||||
'commits/master'
|
|
||||||
).decode('utf8'))
|
|
||||||
git_version = data['commit']['committer']['date']
|
|
||||||
# Strip the last 'Z':
|
|
||||||
git_version = git_version.rsplit('T', 1)[0].replace('-', '.')
|
|
||||||
|
|
||||||
newest = _(
|
|
||||||
'The newest version available online is %(release_version)s, '
|
|
||||||
'or %(git_version)s in Git'
|
|
||||||
) % {'release_version': release_version, 'git_version': git_version}
|
|
||||||
except utils.web.Error as e:
|
except utils.web.Error as e:
|
||||||
self.log.info('Couldn\'t get website version: %s', e)
|
self.log.info('Couldn\'t get website version: %s', e)
|
||||||
newest = _('I couldn\'t fetch the newest version '
|
newest = _('I couldn\'t fetch the newest version '
|
||||||
|
@ -280,7 +280,7 @@ class RSS(callbacks.Plugin):
|
|||||||
raise callbacks.Error(s)
|
raise callbacks.Error(s)
|
||||||
if url:
|
if url:
|
||||||
feed = self.feeds.get(url)
|
feed = self.feeds.get(url)
|
||||||
if feed and feed.name != feed.url and feed.name in self.feed_names:
|
if feed and feed.name != feed.url:
|
||||||
s = format(_('I already have a feed with that URL named %s.'),
|
s = format(_('I already have a feed with that URL named %s.'),
|
||||||
feed.name)
|
feed.name)
|
||||||
raise callbacks.Error(s)
|
raise callbacks.Error(s)
|
||||||
|
@ -84,7 +84,7 @@ def mock_urllib(f):
|
|||||||
|
|
||||||
url = 'http://www.advogato.org/rss/articles.xml'
|
url = 'http://www.advogato.org/rss/articles.xml'
|
||||||
class RSSTestCase(ChannelPluginTestCase):
|
class RSSTestCase(ChannelPluginTestCase):
|
||||||
plugins = ('RSS', 'Plugin')
|
plugins = ('RSS','Plugin')
|
||||||
|
|
||||||
timeout = 1
|
timeout = 1
|
||||||
|
|
||||||
@ -121,27 +121,6 @@ class RSSTestCase(ChannelPluginTestCase):
|
|||||||
self.assertEqual(self.irc.getCallback('RSS').feed_names, {})
|
self.assertEqual(self.irc.getCallback('RSS').feed_names, {})
|
||||||
self.assertTrue(self.irc.getCallback('RSS').get_feed('http://xkcd.com/rss.xml'))
|
self.assertTrue(self.irc.getCallback('RSS').get_feed('http://xkcd.com/rss.xml'))
|
||||||
|
|
||||||
@mock_urllib
|
|
||||||
def testChangeUrl(self, mock):
|
|
||||||
try:
|
|
||||||
self.assertNotError('rss add xkcd http://xkcd.com/rss.xml')
|
|
||||||
self.assertNotError('rss remove xkcd')
|
|
||||||
self.assertNotError('rss add xkcd https://xkcd.com/rss.xml')
|
|
||||||
self.assertRegexp('help xkcd', 'https://')
|
|
||||||
finally:
|
|
||||||
self._feedMsg('rss remove xkcd')
|
|
||||||
|
|
||||||
@mock_urllib
|
|
||||||
def testChangeName(self, mock):
|
|
||||||
try:
|
|
||||||
self.assertNotError('rss add xkcd http://xkcd.com/rss.xml')
|
|
||||||
self.assertNotError('rss remove xkcd')
|
|
||||||
self.assertNotError('rss add xkcd2 http://xkcd.com/rss.xml')
|
|
||||||
self.assertRegexp('help xkcd2', 'http://xkcd.com')
|
|
||||||
finally:
|
|
||||||
self._feedMsg('rss remove xkcd')
|
|
||||||
self._feedMsg('rss remove xkcd2')
|
|
||||||
|
|
||||||
@mock_urllib
|
@mock_urllib
|
||||||
def testInitialAnnounceNewest(self, mock):
|
def testInitialAnnounceNewest(self, mock):
|
||||||
mock._data = xkcd_new
|
mock._data = xkcd_new
|
||||||
|
@ -33,6 +33,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
import pwd
|
import pwd
|
||||||
import sys
|
import sys
|
||||||
|
import crypt
|
||||||
import errno
|
import errno
|
||||||
import random
|
import random
|
||||||
import select
|
import select
|
||||||
@ -40,12 +41,6 @@ import struct
|
|||||||
import subprocess
|
import subprocess
|
||||||
import shlex
|
import shlex
|
||||||
|
|
||||||
try:
|
|
||||||
import crypt
|
|
||||||
except ImportError:
|
|
||||||
# Python >= 3.13
|
|
||||||
crypt = None
|
|
||||||
|
|
||||||
import supybot.conf as conf
|
import supybot.conf as conf
|
||||||
import supybot.utils as utils
|
import supybot.utils as utils
|
||||||
from supybot.commands import *
|
from supybot.commands import *
|
||||||
@ -124,26 +119,25 @@ class Unix(callbacks.Plugin):
|
|||||||
irc.reply(format('%i', os.getpid()), private=True)
|
irc.reply(format('%i', os.getpid()), private=True)
|
||||||
pid = wrap(pid, [('checkCapability', 'owner')])
|
pid = wrap(pid, [('checkCapability', 'owner')])
|
||||||
|
|
||||||
if crypt is not None: # Python < 3.13
|
_cryptre = re.compile(b'[./0-9A-Za-z]')
|
||||||
_cryptre = re.compile(b'[./0-9A-Za-z]')
|
@internationalizeDocstring
|
||||||
@internationalizeDocstring
|
def crypt(self, irc, msg, args, password, salt):
|
||||||
def crypt(self, irc, msg, args, password, salt):
|
"""<password> [<salt>]
|
||||||
"""<password> [<salt>]
|
|
||||||
|
|
||||||
Returns the resulting of doing a crypt() on <password>. If <salt> is
|
Returns the resulting of doing a crypt() on <password>. If <salt> is
|
||||||
not given, uses a random salt. If running on a glibc2 system,
|
not given, uses a random salt. If running on a glibc2 system,
|
||||||
prepending '$1$' to your salt will cause crypt to return an MD5sum
|
prepending '$1$' to your salt will cause crypt to return an MD5sum
|
||||||
based crypt rather than the standard DES based crypt.
|
based crypt rather than the standard DES based crypt.
|
||||||
"""
|
"""
|
||||||
def makeSalt():
|
def makeSalt():
|
||||||
s = b'\x00'
|
s = b'\x00'
|
||||||
while self._cryptre.sub(b'', s) != b'':
|
while self._cryptre.sub(b'', s) != b'':
|
||||||
s = struct.pack('<h', random.randrange(-(2**15), 2**15))
|
s = struct.pack('<h', random.randrange(-(2**15), 2**15))
|
||||||
return s
|
return s
|
||||||
if not salt:
|
if not salt:
|
||||||
salt = makeSalt().decode()
|
salt = makeSalt().decode()
|
||||||
irc.reply(crypt.crypt(password, salt))
|
irc.reply(crypt.crypt(password, salt))
|
||||||
crypt = wrap(crypt, ['something', additional('something')])
|
crypt = wrap(crypt, ['something', additional('something')])
|
||||||
|
|
||||||
@internationalizeDocstring
|
@internationalizeDocstring
|
||||||
def spell(self, irc, msg, args, word):
|
def spell(self, irc, msg, args, word):
|
||||||
|
@ -31,11 +31,6 @@
|
|||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
try:
|
|
||||||
import crypt
|
|
||||||
except ImportError:
|
|
||||||
crypt = None
|
|
||||||
|
|
||||||
from supybot.test import *
|
from supybot.test import *
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -111,9 +106,8 @@ if os.name == 'posix':
|
|||||||
def testProgstats(self):
|
def testProgstats(self):
|
||||||
self.assertNotError('progstats')
|
self.assertNotError('progstats')
|
||||||
|
|
||||||
if crypt is not None: # Python < 3.13
|
def testCrypt(self):
|
||||||
def testCrypt(self):
|
self.assertNotError('crypt jemfinch')
|
||||||
self.assertNotError('crypt jemfinch')
|
|
||||||
|
|
||||||
@skipUnlessFortune
|
@skipUnlessFortune
|
||||||
def testFortune(self):
|
def testFortune(self):
|
||||||
|
6
setup.py
6
setup.py
@ -49,12 +49,10 @@ except ImportError:
|
|||||||
install. This package is pretty standard, and often installed alongside
|
install. This package is pretty standard, and often installed alongside
|
||||||
Python, but it is missing on your system.
|
Python, but it is missing on your system.
|
||||||
Try installing it with your package manager, it is usually called
|
Try installing it with your package manager, it is usually called
|
||||||
'python3-setuptools'; or with '%s -m pip install setuptools'.
|
'python3-setuptools'. If that does not work, try installing python3-pip
|
||||||
If that does not work, try installing python3-pip
|
|
||||||
instead, either with your package manager or by following these
|
instead, either with your package manager or by following these
|
||||||
instructions: https://pip.pypa.io/en/stable/installation/ (replace
|
instructions: https://pip.pypa.io/en/stable/installation/ (replace
|
||||||
'python' with 'python3' in all the commands)"""
|
'python' with 'python3' in all the commands)""")
|
||||||
% sys.executable)
|
|
||||||
sys.stderr.write(os.linesep*2)
|
sys.stderr.write(os.linesep*2)
|
||||||
sys.stderr.write(textwrap.fill(s))
|
sys.stderr.write(textwrap.fill(s))
|
||||||
sys.stderr.write(os.linesep*2)
|
sys.stderr.write(os.linesep*2)
|
||||||
|
@ -419,15 +419,6 @@ def registerNetwork(name, password='', ssl=True, sasl_username='',
|
|||||||
registry.String('', _("""Determines what user modes the bot will request
|
registry.String('', _("""Determines what user modes the bot will request
|
||||||
from the server when it first connects. If empty, defaults to
|
from the server when it first connects. If empty, defaults to
|
||||||
supybot.protocols.irc.umodes""")))
|
supybot.protocols.irc.umodes""")))
|
||||||
registerGlobalValue(network, 'vhost',
|
|
||||||
registry.String('', _("""Determines what vhost the bot will bind to before
|
|
||||||
connecting a server (IRC, HTTP, ...) via IPv4. If empty, defaults to
|
|
||||||
supybot.protocols.irc.vhost""")))
|
|
||||||
registerGlobalValue(network, 'vhostv6',
|
|
||||||
registry.String('', _("""Determines what vhost the bot will bind to before
|
|
||||||
connecting a server (IRC, HTTP, ...) via IPv6. If empty, defaults to
|
|
||||||
supybot.protocols.irc.vhostv6""")))
|
|
||||||
|
|
||||||
sasl = registerGroup(network, 'sasl')
|
sasl = registerGroup(network, 'sasl')
|
||||||
registerGlobalValue(sasl, 'username', registry.String(sasl_username,
|
registerGlobalValue(sasl, 'username', registry.String(sasl_username,
|
||||||
_("""Determines what SASL username will be used on %s. This should
|
_("""Determines what SASL username will be used on %s. This should
|
||||||
|
@ -312,10 +312,8 @@ class SocketDriver(drivers.IrcDriver, drivers.ServersMixin):
|
|||||||
address,
|
address,
|
||||||
port=self.currentServer.port,
|
port=self.currentServer.port,
|
||||||
socks_proxy=socks_proxy,
|
socks_proxy=socks_proxy,
|
||||||
vhost=self.networkGroup.get('vhost')()
|
vhost=conf.supybot.protocols.irc.vhost(),
|
||||||
or conf.supybot.protocols.irc.vhost(),
|
vhostv6=conf.supybot.protocols.irc.vhostv6(),
|
||||||
vhostv6=self.networkGroup.get('vhostv6')()
|
|
||||||
or conf.supybot.protocols.irc.vhostv6(),
|
|
||||||
)
|
)
|
||||||
except socket.error as e:
|
except socket.error as e:
|
||||||
drivers.log.connectError(self.currentServer, e)
|
drivers.log.connectError(self.currentServer, e)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
###
|
###
|
||||||
# Copyright (c) 2011-2024, Valentin Lorentz
|
# Copyright (c) 2011-2021, Valentin Lorentz
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
# Redistribution and use in source and binary forms, with or without
|
# Redistribution and use in source and binary forms, with or without
|
||||||
@ -32,8 +32,8 @@ An embedded and centralized HTTP server for Supybot's plugins.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import cgi
|
||||||
import socket
|
import socket
|
||||||
import urllib.parse
|
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
import supybot.log as log
|
import supybot.log as log
|
||||||
@ -164,114 +164,6 @@ def get_template(filename):
|
|||||||
with open(path + '.example', 'r') as fd:
|
with open(path + '.example', 'r') as fd:
|
||||||
return fd.read()
|
return fd.read()
|
||||||
|
|
||||||
class HttpHeader:
|
|
||||||
__slots__ = ('name', 'value')
|
|
||||||
|
|
||||||
def __init__(self, name, value):
|
|
||||||
self.name = name
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
"""Return printable representation."""
|
|
||||||
return "HttpHeader(%r, %r)" % (self.name, self.value)
|
|
||||||
|
|
||||||
class HttpHeaders:
|
|
||||||
"""Copy of `cgi.FieldStorage
|
|
||||||
<https://github.com/python/cpython/blob/v3.12.3/Lib/cgi.py#L512-L594>`
|
|
||||||
before it was removed from the stdlib.
|
|
||||||
"""
|
|
||||||
__slots__ = ('list',)
|
|
||||||
def __init__(self, headers):
|
|
||||||
self.list = headers
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'HttpHeaders(%r)' % self.list
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return iter(self.keys())
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
if name != 'value':
|
|
||||||
raise AttributeError(name)
|
|
||||||
if self.file:
|
|
||||||
self.file.seek(0)
|
|
||||||
value = self.file.read()
|
|
||||||
self.file.seek(0)
|
|
||||||
elif self.list is not None:
|
|
||||||
value = self.list
|
|
||||||
else:
|
|
||||||
value = None
|
|
||||||
return value
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
"""Dictionary style indexing."""
|
|
||||||
if self.list is None:
|
|
||||||
raise TypeError("not indexable")
|
|
||||||
found = []
|
|
||||||
for item in self.list:
|
|
||||||
if item.name == key: found.append(item)
|
|
||||||
if not found:
|
|
||||||
raise KeyError(key)
|
|
||||||
if len(found) == 1:
|
|
||||||
return found[0]
|
|
||||||
else:
|
|
||||||
return found
|
|
||||||
|
|
||||||
def getvalue(self, key, default=None):
|
|
||||||
"""Dictionary style get() method, including 'value' lookup."""
|
|
||||||
if key in self:
|
|
||||||
value = self[key]
|
|
||||||
if isinstance(value, list):
|
|
||||||
return [x.value for x in value]
|
|
||||||
else:
|
|
||||||
return value.value
|
|
||||||
else:
|
|
||||||
return default
|
|
||||||
|
|
||||||
def getfirst(self, key, default=None):
|
|
||||||
""" Return the first value received."""
|
|
||||||
if key in self:
|
|
||||||
value = self[key]
|
|
||||||
if isinstance(value, list):
|
|
||||||
return value[0].value
|
|
||||||
else:
|
|
||||||
return value.value
|
|
||||||
else:
|
|
||||||
return default
|
|
||||||
|
|
||||||
def getlist(self, key):
|
|
||||||
""" Return list of received values."""
|
|
||||||
if key in self:
|
|
||||||
value = self[key]
|
|
||||||
if isinstance(value, list):
|
|
||||||
return [x.value for x in value]
|
|
||||||
else:
|
|
||||||
return [value.value]
|
|
||||||
else:
|
|
||||||
return []
|
|
||||||
|
|
||||||
def keys(self):
|
|
||||||
"""Dictionary style keys() method."""
|
|
||||||
if self.list is None:
|
|
||||||
raise TypeError("not indexable")
|
|
||||||
return list(set(item.name for item in self.list))
|
|
||||||
|
|
||||||
def __contains__(self, key):
|
|
||||||
"""Dictionary style __contains__ method."""
|
|
||||||
if self.list is None:
|
|
||||||
raise TypeError("not indexable")
|
|
||||||
return any(item.name == key for item in self.list)
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
"""Dictionary style len(x) support."""
|
|
||||||
return len(self.keys())
|
|
||||||
|
|
||||||
def __bool__(self):
|
|
||||||
if self.list is None:
|
|
||||||
raise TypeError("Cannot be converted to bool.")
|
|
||||||
return bool(self.list)
|
|
||||||
|
|
||||||
|
|
||||||
class SupyHTTPRequestHandler(BaseHTTPRequestHandler):
|
class SupyHTTPRequestHandler(BaseHTTPRequestHandler):
|
||||||
def do_X(self, callbackMethod, *args, **kwargs):
|
def do_X(self, callbackMethod, *args, **kwargs):
|
||||||
if self.path == '/':
|
if self.path == '/':
|
||||||
@ -307,11 +199,12 @@ class SupyHTTPRequestHandler(BaseHTTPRequestHandler):
|
|||||||
if 'Content-Type' not in self.headers:
|
if 'Content-Type' not in self.headers:
|
||||||
self.headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
self.headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
||||||
if self.headers['Content-Type'] == 'application/x-www-form-urlencoded':
|
if self.headers['Content-Type'] == 'application/x-www-form-urlencoded':
|
||||||
length = min(100000, int(self.headers.get('Content-Length', '100000')))
|
form = cgi.FieldStorage(
|
||||||
qs = self.rfile.read(length).decode()
|
fp=self.rfile,
|
||||||
form = HttpHeaders([
|
headers=self.headers,
|
||||||
HttpHeader(k, v) for (k, v) in urllib.parse.parse_qsl(qs)
|
environ={'REQUEST_METHOD':'POST',
|
||||||
])
|
'CONTENT_TYPE':self.headers['Content-Type'],
|
||||||
|
})
|
||||||
else:
|
else:
|
||||||
content_length = int(self.headers.get('Content-Length', '0'))
|
content_length = int(self.headers.get('Content-Length', '0'))
|
||||||
form = self.rfile.read(content_length)
|
form = self.rfile.read(content_length)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user