diff --git a/davtelepot/authorization.py b/davtelepot/authorization.py
new file mode 100644
index 0000000..ba1c27b
--- /dev/null
+++ b/davtelepot/authorization.py
@@ -0,0 +1,224 @@
+"""Provide authorization levels to bot functions."""
+
+# Standard library modules
+from collections import OrderedDict
+
+# Project modules
+from .utilities import make_button
+
+DEFAULT_ROLES = OrderedDict()
+DEFAULT_ROLES[0] = {
+ 'name': 'banned',
+ 'symbol': '🚫',
+ 'singular': 'banned',
+ 'plural': 'banned',
+ 'can_appoint': [],
+ 'can_be_appointed_by': [1, 2, 3]
+}
+DEFAULT_ROLES[1] = {
+ 'name': 'founder',
+ 'symbol': '👑',
+ 'singular': 'founder',
+ 'plural': 'founders',
+ 'can_appoint': [0, 1, 2, 3, 4, 5, 7, 100],
+ 'can_be_appointed_by': []
+}
+DEFAULT_ROLES[2] = {
+ 'name': 'admin',
+ 'symbol': '⚜️',
+ 'singular': 'administrator',
+ 'plural': 'administrators',
+ 'can_appoint': [0, 3, 4, 5, 7, 100],
+ 'can_be_appointed_by': [1]
+}
+DEFAULT_ROLES[3] = {
+ 'name': 'moderator',
+ 'symbol': '🔰',
+ 'singular': 'moderator',
+ 'plural': 'moderators',
+ 'can_appoint': [0, 5, 7],
+ 'can_be_appointed_by': [1, 2]
+}
+DEFAULT_ROLES[5] = {
+ 'name': 'user',
+ 'symbol': '🎫',
+ 'singular': 'registered user',
+ 'plural': 'registered users',
+ 'can_appoint': [],
+ 'can_be_appointed_by': [1, 2, 3]
+}
+DEFAULT_ROLES[100] = {
+ 'name': 'everybody',
+ 'symbol': '👤',
+ 'singular': 'common user',
+ 'plural': 'common users',
+ 'can_appoint': [],
+ 'can_be_appointed_by': [1, 2, 3]
+}
+
+
+class Role():
+ """Authorization level for users of a bot."""
+
+ roles = OrderedDict()
+
+ def __init__(self, code, name, symbol, singular, plural,
+ can_appoint, can_be_appointed_by):
+ """Instantiate Role object.
+
+ code : int
+ The higher the code, the less privileges are connected to that
+ role.
+ Use 0 for banned users.
+ name : str
+ Short name for role.
+ symbol : str
+ Emoji used to represent role.
+ singular : str
+ Singular full name of role.
+ plural : str
+ Plural full name of role.
+ can_appoint : lsit of int
+ List of role codes that this role can appoint.
+ can_be_appointed_by : list of int
+ List of role codes this role can be appointed by.
+ """
+ self._code = code
+ self._name = name
+ self._symbol = symbol
+ self._singular = singular
+ self._plural = plural
+ self._can_appoint = can_appoint
+ self._can_be_appointed_by = can_be_appointed_by
+ self.__class__.roles[self.code] = self
+
+ @property
+ def code(self):
+ """Return code."""
+ return self._code
+
+ @property
+ def name(self):
+ """Return name."""
+ return self._name
+
+ @property
+ def symbol(self):
+ """Return symbol."""
+ return self._symbol
+
+ @property
+ def singular(self):
+ """Return singular."""
+ return self._singular
+
+ @property
+ def plural(self):
+ """Return plural."""
+ return self._plural
+
+ @property
+ def can_appoint(self):
+ """Return can_appoint."""
+ return self._can_appoint
+
+ @property
+ def can_be_appointed_by(self):
+ """Return roles whom this role can be appointed by."""
+ return self._can_be_appointed_by
+
+ @classmethod
+ def get_by_role_id(cls, role_id=100):
+ """Give a `role_id`, return the corresponding `Role` instance."""
+ for code, role in cls.roles.items():
+ if code == role_id:
+ return role
+ raise IndexError(f"Unknown role id: {role_id}")
+
+ @classmethod
+ def get_user_role(cls, user_record=None, role_id=None):
+ """Given a `user_record`, return its `Role`.
+
+ `role_id` may be passed as keyword argument or as user_record.
+ """
+ if isinstance(user_record, dict) and 'privileges' in user_record:
+ user_role_id = user_record['privileges']
+ elif type(user_record) is int:
+ user_role_id = user_record
+ if type(user_role_id) is not int:
+ user_role_id = 100
+ return cls.get_by_role_id(role_id=role_id)
+
+ @classmethod
+ def get_user_role_panel(cls, user_record):
+ """Get text and buttons for user role panel."""
+ user_role = cls.get_user_role(user_record)
+ text = (
+ """👤 {u[username]}\n"""
+ f"🔑 {user_role.singular.capitalize()} {user_role.symbol}"
+ ).format(
+ u=user_record,
+ )
+ buttons = [
+ make_button(
+ f"{role.symbol} {role.singular.capitalize()}",
+ f"auth:///set|{user_record['id']}_{code}"
+ )
+ for code, role in cls.roles.items()
+ ]
+ return text, buttons
+
+ def __eq__(self, other):
+ """Return True if self is equal to other."""
+ return self.code == other.code
+
+ def __gt__(self, other):
+ """Return True if self can appoint other."""
+ return (
+ self.code < other.code
+ and self.code in other.can_be_appointed_by
+ )
+
+ def __ge__(self, other):
+ """Return True if self >= other."""
+ return self.__gt__(other) or self.__eq__(other)
+
+ def __lt__(self, other):
+ """Return True if self can not appoint other."""
+ return not self.__ge__(other)
+
+ def __le__(self, other):
+ """Return True if self is superior or equal to other."""
+ return not self.__gt__(other)
+
+ def __ne__(self, other):
+ """Return True if self is not equal to other."""
+ return not self.__eq__(other)
+
+ def __str__(self):
+ """Return human-readable description of role."""
+ return f""
+
+
+def init(bot, roles=None):
+ """Set bot roles and assign role-related commands.
+
+ Pass an OrderedDict of `roles` to get them set.
+ """
+ class _Role(Role):
+ roles = OrderedDict()
+
+ if roles is None:
+ roles = DEFAULT_ROLES
+ # Cast roles to OrderedDict
+ if isinstance(roles, list):
+ roles = OrderedDict(
+ (i, element)
+ for i, element in enumerate(list)
+ )
+ if not isinstance(roles, OrderedDict):
+ raise TypeError("`roles` shall be a OrderedDict!")
+ for id, role in roles.items():
+ if 'code' not in role:
+ role['code'] = id
+ Role(**role)