Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion sopel/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
Optional,
Tuple,
TYPE_CHECKING,
TypeVar,
Union,
)

Expand All @@ -46,6 +47,8 @@

LOGGER = logging.getLogger(__name__)

AbstractRuleType = TypeVar('AbstractRuleType', bound=plugin_rules.AbstractRule)


class Sopel(irc.AbstractBot):
def __init__(self, config, daemon=False):
Expand Down Expand Up @@ -594,7 +597,7 @@ def register_urls(self, urls: Iterable) -> None:

def rate_limit_info(
self,
rule: plugin_rules.Rule,
rule: AbstractRuleType,
trigger: Trigger,
) -> Tuple[bool, Optional[str]]:
if trigger.admin or rule.is_unblockable():
Expand Down Expand Up @@ -627,6 +630,11 @@ def rate_limit_info(
else:
return False, None

if not metrics.last_time:
# you and I know that is_limited() will never return True if
# last_time is None, but the type-checker doesn't
return False, None

next_time = metrics.last_time + rate_limit
time_left = next_time - at_time

Expand Down
4 changes: 2 additions & 2 deletions sopel/irc/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,10 +313,10 @@ async def read_forever(self) -> None:
while not self._reader.at_eof():
try:
line: bytes = await self._reader.readuntil(separator=b'\r\n')
except asyncio.exceptions.IncompleteReadError as err:
except asyncio.IncompleteReadError as err:
LOGGER.warning('Receiving partial message from IRC.')
line = err.partial
except asyncio.exceptions.LimitOverrunError:
except asyncio.LimitOverrunError:
LOGGER.exception('Unable to read from IRC server.')
break

Expand Down
2 changes: 1 addition & 1 deletion sopel/modules/url.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ def process_urls(
if (shorten_url_length > 0) and (len(url) > shorten_url_length):
tinyurl = get_or_create_shorturl(bot, url)

yield (url, title, parsed_url.hostname, tinyurl, False)
yield URLInfo(url, title, parsed_url.hostname, tinyurl, False)


def check_callbacks(bot: SopelWrapper, url: str, use_excludes: bool = True) -> bool:
Expand Down
38 changes: 32 additions & 6 deletions sopel/plugins/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -481,11 +481,10 @@ def set_return_value(self, value: Any) -> None:
def last_time(self) -> Optional[datetime.datetime]:
"""Last recorded start/end time for the associated rule"""
# detect if we just started something or if it ended
last_time = self.started_at
if self.ended_at and self.started_at < self.ended_at:
last_time = self.ended_at
if (self.started_at and self.ended_at) and (self.started_at < self.ended_at):
return self.ended_at

return last_time
return self.started_at

def is_limited(
self,
Expand All @@ -501,7 +500,7 @@ def is_limited(
if self.last_return_value == IGNORE_RATE_LIMIT:
return False

return self.last_time > time_limit
return self.last_time > time_limit if self.last_time else False

def __enter__(self) -> RuleMetrics:
self.start()
Expand Down Expand Up @@ -740,6 +739,33 @@ def is_unblockable(self) -> bool:
:return: ``True`` when the rule is unblockable, ``False`` otherwise
"""

@abc.abstractmethod
def get_user_metrics(self, nick: Identifier) -> RuleMetrics:
"""Get the rule's usage metrics for the given user."""

@abc.abstractmethod
def get_channel_metrics(self, channel: Identifier) -> RuleMetrics:
"""Get the rule's usage metrics for the given channel."""

@abc.abstractmethod
def get_global_metrics(self) -> RuleMetrics:
"""Get the rule's global usage metrics."""

@property
@abc.abstractmethod
def user_rate_limit(self) -> datetime.timedelta:
"""The rule's user rate limit."""

@property
@abc.abstractmethod
def channel_rate_limit(self) -> datetime.timedelta:
"""The rule's channel rate limit."""

@property
@abc.abstractmethod
def global_rate_limit(self) -> datetime.timedelta:
"""The rule's global rate limit."""

@abc.abstractmethod
def is_user_rate_limited(
self,
Expand Down Expand Up @@ -799,7 +825,7 @@ def channel_rate_template(self) -> Optional[str]:
:return: A formatted string, or ``None`` if no message is set.

This method is called by the bot when a trigger hits the channel rate
limit (i.e. for the specificed ``channel``).
limit (i.e. for the specified ``channel``).
"""

@property
Expand Down