#!/usr/bin/env python
# matrixctl
# Copyright (c) 2020 Michael Sasser <Michael@MichaelSasser.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Use the functions of this module as printing helpers."""
from __future__ import annotations
import logging
import re
import typing as t
from contextlib import suppress
from enum import Enum
from enum import unique
__author__: str = "Michael Sasser"
__email__: str = "Michael@MichaelSasser.org"
logger = logging.getLogger(__name__)
EVENT_ID_PATTERN: t.Pattern[str] = re.compile(r"^\$[0-9a-zA-Z.=_-]{1,255}$")
USER_ID_PATTERN: t.Pattern[str] = re.compile(r"^\@.*\:.*\..*$")
ROOM_ID_PATTERN: t.Pattern[str] = re.compile(r"^\!.*\:.*\..*$")
[docs]@unique
class MessageType(Enum):
"""Use this enum for describing message types.
Supported events:
===================== ===================================================
message_type Usage
===================== ===================================================
m.room.message This event is used when sending messages in a room
m.room.name This event sets the name of an room
m.room.topic This events sets the room topic
m.room.avatar This event sets the room avatar
m.room.pinned_events This event pins events
m.room.member Adjusts the membership state for a user in a room
m.room.join_rules This event sets the join rules
m.room.create This event creates a room
m.room.power_levels This event sets a rooms power levels
m.room.redaction This event redacts other events
===================== ===================================================
"""
M_ROOM_MESSAGE = "m.room.message"
M_ROOM_NAME = "m.room.name"
M_ROOM_TOPIC = "m.room.topic"
M_ROOM_AVATAR = "m.room.avatar"
M_ROOM_PINNED_EVENTS = "m.room.pinned_events"
M_ROOM_MEMBER = "m.room.member"
M_ROOM_JOIN_RULES = "m.room.join_rules"
M_ROOM_CREATE = "m.room.create"
M_ROOM_POWER_LEVELS = "m.room.power_levels"
M_ROOM_REDACTION = "m.room.redaction"
[docs]def sanitize_message_type(
message_type: str | MessageType | None,
) -> MessageType | t.Literal[False] | None:
"""Sanitize an message type.
Examples
--------
>>> sanitize_message_type("m.room.message")
<MessageType.M_ROOM_MESSAGE: 'm.room.message'>
>>> sanitize_message_type("M.RooM.MeSsAgE")
<MessageType.M_ROOM_MESSAGE: 'm.room.message'>
>>> sanitize_message_type(" m.room.message ")
<MessageType.M_ROOM_MESSAGE: 'm.room.message'>
>>> sanitize_message_type(MessageType.M_ROOM_MESSAGE)
<MessageType.M_ROOM_MESSAGE: 'm.room.message'>
>>> sanitize_message_type("something invalid")
False
>>> sanitize_message_type(None)
Parameters
----------
message_type : typing.Any
The event identifier to sanitize
Returns
-------
message_type_sanitized : typing.Literal[False] or MessageType, optional
The function returns ``None`` if ``message_type`` is ``None``,
``MessageType``, if it is valid, otherwise ``False``
"""
if isinstance(message_type, MessageType) or message_type is None:
return message_type
with suppress(TypeError, KeyError, AttributeError):
return MessageType[message_type.strip().replace(".", "_").upper()]
logger.error("The message type is not wrong.")
return False
[docs]def sanitize(
pattern: t.Pattern[str],
identifier: t.Any | None,
error_message: str,
) -> str | t.Literal[False] | None:
"""Create a new sanitizer based on compiled RegEx expressions.
A helper function for simplifying the latter sanitize identifier specific
functions.
Parameters
----------
pattern : typing.Pattern
The RegEx pattern used for the specific sanitizing
identifier : typing.Any, optional
The identifier to sanitize based on the pattern
error_message : str
The error string used for logging errors
Returns
-------
result : typing.Literal[False] or str, optional
The function returns ``None`` if ``identifier`` is ``None``,
the sanitized string, when it is valid, otherwise ``False``
"""
if identifier is None:
return None
with suppress(TypeError, AttributeError):
identifier = str(identifier).strip()
if pattern.match(identifier):
return t.cast(str, identifier)
logger.error(error_message)
return False
[docs]def sanitize_event_identifier(
event_identifier: t.Any,
) -> str | t.Literal[False] | None:
"""Sanitize an event identifier.
Examples
--------
>>> sanitize_event_identifier(
... "$event-abcdefghijklmH4omLrEumu7Pd01Qp-LySpK_Y"
... )
'$event-abcdefghijklmH4omLrEumu7Pd01Qp-LySpK_Y'
>>> sanitize_event_identifier(
... " $event-abcdefghijklmH4omLrEumu7Pd01Qp-LySpK_Y "
... )
'$event-abcdefghijklmH4omLrEumu7Pd01Qp-LySpK_Y'
>>> sanitize_event_identifier("something invalid")
False
>>> sanitize_event_identifier(None)
Parameters
----------
event_identifier : typeing.Any
The event identifier to sanitize
Returns
-------
result : typing.Literal[False] or str, optional
The function returns ``None`` if ``event_identifier`` is ``None``,
the sanitized string, when it is valid, otherwise ``False``
"""
return sanitize(
pattern=EVENT_ID_PATTERN,
identifier=event_identifier,
error_message=(
"The given event identifier has an invalid format. Please make"
" sure you use one with the correct format. For example:"
" $tjeDdqYAk9BDLAUcniGUy640e_D9TrWU2RmCksJQQEQ"
),
)
[docs]def sanitize_user_identifier(
user_identifier: t.Any,
) -> str | t.Literal[False] | None:
"""Sanitize an user identifier.
Examples
--------
>>> sanitize_user_identifier(
... "@user:domain.tld"
... )
'@user:domain.tld'
>>> sanitize_user_identifier(
... " @user:domain.tld "
... )
'@user:domain.tld'
>>> sanitize_user_identifier("something invalid")
False
>>> sanitize_user_identifier(None)
Parameters
----------
user_identifier : typing.Any
The user identifier to sanitize
Returns
-------
event_identifier_sanitized : typing.Literal[False] or str, optional
The function returns ``None`` if ``user_identifier`` is ``None``,
the sanitized string, when it is valid, otherwise ``False``
"""
return sanitize(
pattern=USER_ID_PATTERN,
identifier=user_identifier,
error_message=(
"The given user identifier has an invalid format. Please make sure"
" you use one with the correct format. For example:"
" @username:domain.tld"
),
)
[docs]def sanitize_room_identifier(
room_identifier: t.Any,
) -> str | t.Literal[False] | None:
"""Sanitize an room identifier.
Examples
--------
>>> sanitize_room_identifier(
... "!room:domain.tld"
... )
'!room:domain.tld'
>>> sanitize_room_identifier(
... " !room:domain.tld "
... )
'!room:domain.tld'
>>> sanitize_room_identifier("something invalid")
False
>>> sanitize_room_identifier(None)
Parameters
----------
room_identifier : typing.Any
The room identifier to sanitize
Returns
-------
room_identifier_sanitized : typing.Literal[False] or str, optional
The function returns ``None`` if ``room_identifier`` is ``None``,
the sanitized string, when it is valid, otherwise ``False``
"""
return sanitize(
pattern=ROOM_ID_PATTERN,
identifier=room_identifier,
error_message=(
"The given room identifier has an invalid format. Please make sure"
" you use one with the correct format. For example:"
" !iuyQXswfjgxQMZGrfQ:matrix.org"
),
)
# vim: set ft=python :