test
This commit is contained in:
parent
15b2c585c1
commit
62a39f71e7
@ -1,8 +1,9 @@
|
||||
|
||||
from .config import Config
|
||||
from .versions import Versions
|
||||
from .logger import Logger
|
||||
from .database import Database
|
||||
from .interface.config import Config
|
||||
from .interface.versions import Versions
|
||||
from .interface.logger import Logger
|
||||
from .interface.database import Database
|
||||
from .interface.comms import Comms
|
||||
|
||||
|
||||
class SpiceBotCore_OBJ():
|
||||
@ -28,6 +29,10 @@ class SpiceBotCore_OBJ():
|
||||
self.database = Database(self.config)
|
||||
self.logger.info("SpiceBot Database Interface Setup Complete.")
|
||||
|
||||
# Bypass Sopel's method for writing to IRC
|
||||
self.comms = Comms(self.config)
|
||||
self.logger.info("SpiceBot Comms Interface Setup Complete.")
|
||||
|
||||
def setup(self, bot):
|
||||
"""This runs with the plugin setup routine"""
|
||||
|
||||
@ -36,3 +41,7 @@ class SpiceBotCore_OBJ():
|
||||
|
||||
# Re-initialize the bot config properly during plugin setup routine
|
||||
self.config.config = bot.config
|
||||
|
||||
# OSD shortcut
|
||||
def osd(self, messages, recipients=None, text_method='PRIVMSG', max_messages=-1):
|
||||
return self.comms.osd(messages, recipients, text_method, max_messages)
|
||||
|
||||
247
SpiceBot/SpiceBotCore/interface/comms/__init__.py
Normal file
247
SpiceBot/SpiceBotCore/interface/comms/__init__.py
Normal file
@ -0,0 +1,247 @@
|
||||
# coding=utf8
|
||||
from __future__ import unicode_literals, absolute_import, division, print_function
|
||||
|
||||
from sopel.tools import Identifier
|
||||
from sopel.irc.utils import safe
|
||||
|
||||
import threading
|
||||
import sys
|
||||
if sys.version_info.major >= 3:
|
||||
from collections import abc
|
||||
|
||||
if sys.version_info.major >= 3:
|
||||
unicode = str
|
||||
|
||||
|
||||
class Comms():
|
||||
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
|
||||
self.backend = None
|
||||
self.sending = threading.RLock()
|
||||
self.stack = {}
|
||||
|
||||
def append_bot(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
@property
|
||||
def botnick(self):
|
||||
return self.config.core.nick
|
||||
|
||||
@property
|
||||
def bothostmask(self):
|
||||
return self.bot.hostmask
|
||||
|
||||
def write(self, args, text=None):
|
||||
while not self.backend:
|
||||
pass
|
||||
args = [safe(arg) for arg in args]
|
||||
self.backend.send_command(*args, text=text)
|
||||
|
||||
def get_message_recipientgroups(self, recipients, text_method):
|
||||
"""
|
||||
Split recipients into groups based on server capabilities.
|
||||
This defaults to 4
|
||||
Input can be
|
||||
* unicode string
|
||||
* a comma-seperated unicode string
|
||||
* list
|
||||
* dict_keys handy for list(bot.channels.keys())
|
||||
"""
|
||||
|
||||
if sys.version_info.major >= 3:
|
||||
if isinstance(recipients, abc.KeysView):
|
||||
recipients = [x for x in recipients]
|
||||
if isinstance(recipients, dict):
|
||||
recipients = [x for x in recipients]
|
||||
|
||||
if not isinstance(recipients, list):
|
||||
recipients = recipients.split(",")
|
||||
|
||||
if not len(recipients):
|
||||
raise ValueError("Recipients list empty.")
|
||||
|
||||
if text_method == 'NOTICE':
|
||||
maxtargets = 4
|
||||
elif text_method in ['PRIVMSG', 'ACTION']:
|
||||
maxtargets = 4
|
||||
maxtargets = int(maxtargets)
|
||||
|
||||
recipientgroups = []
|
||||
while len(recipients):
|
||||
recipients_part = ','.join(x for x in recipients[-maxtargets:])
|
||||
recipientgroups.append(recipients_part)
|
||||
del recipients[-maxtargets:]
|
||||
|
||||
return recipientgroups
|
||||
|
||||
def get_available_message_bytes(self, recipientgroups, text_method):
|
||||
"""
|
||||
Get total available bytes for sending a message line
|
||||
Total sendable bytes is 512
|
||||
* 15 are reserved for basic IRC NOTICE/PRIVMSG and a small buffer.
|
||||
* The bots hostmask plays a role in this count
|
||||
Note: if unavailable, we calculate the maximum length of a hostmask
|
||||
* The recipients we send to also is a factor. Multiple recipients reduces
|
||||
sendable message length
|
||||
"""
|
||||
|
||||
if text_method == 'ACTION':
|
||||
text_method_bytes = (len('PRIVMSG')
|
||||
+ len("\x01ACTION \x01")
|
||||
)
|
||||
else:
|
||||
text_method_bytes = len(text_method)
|
||||
|
||||
if self.bothostmask:
|
||||
hostmaskbytes = len((self.bothostmask).encode('utf-8'))
|
||||
else:
|
||||
hostmaskbytes = (len((self.botnick).encode('utf-8')) # Bot's NICKLEN
|
||||
+ 1 # (! separator)
|
||||
+ len('~') # (for the optional ~ in user)
|
||||
+ 9 # max username length
|
||||
+ 1 # (@ separator)
|
||||
+ 63 # <hostname> has a maximum length of 63 characters.
|
||||
)
|
||||
|
||||
# find the maximum target group length, and use the max
|
||||
groupbytes = []
|
||||
for recipients_part in recipientgroups:
|
||||
groupbytes.append(len((recipients_part).encode('utf-8')))
|
||||
|
||||
max_recipients_bytes = max(groupbytes)
|
||||
|
||||
allowedLength = (512
|
||||
- len(':') - hostmaskbytes
|
||||
- len(' ') - text_method_bytes - len(' ')
|
||||
- max_recipients_bytes
|
||||
- len(' :')
|
||||
- len('\r\n')
|
||||
)
|
||||
|
||||
return allowedLength
|
||||
|
||||
def get_sendable_message_list(self, messages, max_length=400):
|
||||
"""Get a sendable ``text`` message list.
|
||||
:param str txt: unicode string of text to send
|
||||
:param int max_length: maximum length of the message to be sendable
|
||||
:return: a tuple of two values, the sendable text and its excess text
|
||||
We're arbitrarily saying that the max is 400 bytes of text when
|
||||
messages will be split. Otherwise, we'd have to account for the bot's
|
||||
hostmask, which is hard.
|
||||
The `max_length` is the max length of text in **bytes**, but we take
|
||||
care of unicode 2-bytes characters, by working on the unicode string,
|
||||
then making sure the bytes version is smaller than the max length.
|
||||
"""
|
||||
|
||||
if not isinstance(messages, list):
|
||||
messages = [messages]
|
||||
|
||||
messages_list = ['']
|
||||
message_padding = 4 * " "
|
||||
|
||||
for message in messages:
|
||||
if len((messages_list[-1] + message_padding + message).encode('utf-8')) <= max_length:
|
||||
if messages_list[-1] == '':
|
||||
messages_list[-1] = message
|
||||
else:
|
||||
messages_list[-1] = messages_list[-1] + message_padding + message
|
||||
else:
|
||||
text_list = []
|
||||
while len(message.encode('utf-8')) > max_length and not message.isspace():
|
||||
last_space = message.rfind(' ', 0, max_length)
|
||||
if last_space == -1:
|
||||
# No last space, just split where it is possible
|
||||
splitappend = message[:max_length]
|
||||
if not splitappend.isspace():
|
||||
text_list.append(splitappend)
|
||||
message = message[max_length:]
|
||||
else:
|
||||
# Split at the last best space found
|
||||
splitappend = message[:last_space]
|
||||
if not splitappend.isspace():
|
||||
text_list.append(splitappend)
|
||||
message = message[last_space:]
|
||||
if len(message.encode('utf-8')) and not message.isspace():
|
||||
text_list.append(message)
|
||||
messages_list.extend(text_list)
|
||||
|
||||
return messages_list
|
||||
|
||||
def osd(self, messages, recipients=None, text_method='PRIVMSG', max_messages=-1):
|
||||
"""Send ``text`` as a PRIVMSG, CTCP ACTION, or NOTICE to ``recipients``.
|
||||
In the context of a triggered callable, the ``recipient`` defaults to
|
||||
the channel (or nickname, if a private message) from which the message
|
||||
was received.
|
||||
By default, unless specified in the configuration file, there is some
|
||||
built-in flood protection. Messages displayed over 5 times in 2 minutes
|
||||
will be displayed as '...'.
|
||||
The ``recipient`` can be in list format or a comma seperated string,
|
||||
with the ability to send to multiple recipients simultaneously. The
|
||||
default recipients that the bot will send to is 4 if the IRC server
|
||||
doesn't specify a limit for TARGMAX.
|
||||
Text can be sent to this function in either string or list format.
|
||||
List format will insert as small buffering space between entries in the
|
||||
list.
|
||||
There are 512 bytes available in a single IRC message. This includes
|
||||
hostmask of the bot as well as around 15 bytes of reserved IRC message
|
||||
type. This also includes the destinations/recipients of the message.
|
||||
This will split given strings/lists into a displayable format as close
|
||||
to the maximum 512 bytes as possible.
|
||||
If ``max_messages`` is given, the split mesage will display in as many
|
||||
lines specified by this argument. Specifying ``0`` or a negative number
|
||||
will display without limitation. By default this is set to ``-1`` when
|
||||
called directly. When called from the say/msg/reply/notice/action it
|
||||
will default to ``1``.
|
||||
"""
|
||||
|
||||
if not hasattr(self, 'stack'):
|
||||
self.stack = {}
|
||||
|
||||
text_method = text_method.upper()
|
||||
if text_method == 'SAY' or text_method not in ['NOTICE', 'ACTION']:
|
||||
text_method = 'PRIVMSG'
|
||||
|
||||
recipientgroups = self.get_message_recipientgroups(recipients, text_method)
|
||||
available_bytes = self.get_available_message_bytes(recipientgroups, text_method)
|
||||
messages_list = self.get_sendable_message_list(messages, available_bytes)
|
||||
|
||||
if max_messages >= 1:
|
||||
messages_list = messages_list[:max_messages]
|
||||
|
||||
text_method_orig = text_method
|
||||
|
||||
for recipientgroup in recipientgroups:
|
||||
text_method = text_method_orig
|
||||
|
||||
recipient_id = Identifier(recipientgroup)
|
||||
|
||||
recipient_stack = self.stack.setdefault(recipient_id, {
|
||||
'messages': [],
|
||||
'flood_left': 999,
|
||||
'dots': 0,
|
||||
})
|
||||
recipient_stack['dots'] = 0
|
||||
|
||||
with self.sending:
|
||||
|
||||
for text in messages_list:
|
||||
|
||||
if recipient_stack['dots'] <= 3:
|
||||
if text_method == 'ACTION':
|
||||
text = '\001ACTION {}\001'.format(text)
|
||||
self.write(('PRIVMSG', recipientgroup), text)
|
||||
text_method = 'PRIVMSG'
|
||||
elif text_method == 'NOTICE':
|
||||
self.write(('NOTICE', recipientgroup), text)
|
||||
else:
|
||||
self.write(('PRIVMSG', recipientgroup), text)
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""
|
||||
Quick and dirty shortcuts. Will only get called for undefined attributes.
|
||||
"""
|
||||
|
||||
if hasattr(self.bot, name):
|
||||
return eval("self.bot.%s" % name)
|
||||
Loading…
Reference in New Issue
Block a user