summaryrefslogtreecommitdiffstats
path: root/telepot/delegate.py
diff options
context:
space:
mode:
authorJan Tuomi <jan@jantuomi.fi>2025-07-19 10:42:15 +0300
committerJan Tuomi <jan@jantuomi.fi>2025-07-19 10:42:15 +0300
commit5a636abf7e98a5687811167917d0e07103599bc8 (patch)
tree682f8d70d194c6bf02b750780f05689fcb83ba5a /telepot/delegate.py
parente1ae18d1bc9e26f8ab817ca1c1e4e271badf02ad (diff)
Vendor the unmaintained telepot dependency
Diffstat (limited to 'telepot/delegate.py')
-rw-r--r--telepot/delegate.py420
1 files changed, 420 insertions, 0 deletions
diff --git a/telepot/delegate.py b/telepot/delegate.py
new file mode 100644
index 0000000..91b981e
--- /dev/null
+++ b/telepot/delegate.py
@@ -0,0 +1,420 @@
+import traceback
+from functools import wraps
+from . import exception
+from . import flavor, peel, is_event, chat_flavors, inline_flavors
+
+def _wrap_none(fn):
+ def w(*args, **kwargs):
+ try:
+ return fn(*args, **kwargs)
+ except (KeyError, exception.BadFlavor):
+ return None
+ return w
+
+def per_chat_id(types='all'):
+ """
+ :param types:
+ ``all`` or a list of chat types (``private``, ``group``, ``channel``)
+
+ :return:
+ a seeder function that returns the chat id only if the chat type is in ``types``.
+ """
+ return _wrap_none(lambda msg:
+ msg['chat']['id']
+ if types == 'all' or msg['chat']['type'] in types
+ else None)
+
+def per_chat_id_in(s, types='all'):
+ """
+ :param s:
+ a list or set of chat id
+
+ :param types:
+ ``all`` or a list of chat types (``private``, ``group``, ``channel``)
+
+ :return:
+ a seeder function that returns the chat id only if the chat id is in ``s``
+ and chat type is in ``types``.
+ """
+ return _wrap_none(lambda msg:
+ msg['chat']['id']
+ if (types == 'all' or msg['chat']['type'] in types) and msg['chat']['id'] in s
+ else None)
+
+def per_chat_id_except(s, types='all'):
+ """
+ :param s:
+ a list or set of chat id
+
+ :param types:
+ ``all`` or a list of chat types (``private``, ``group``, ``channel``)
+
+ :return:
+ a seeder function that returns the chat id only if the chat id is *not* in ``s``
+ and chat type is in ``types``.
+ """
+ return _wrap_none(lambda msg:
+ msg['chat']['id']
+ if (types == 'all' or msg['chat']['type'] in types) and msg['chat']['id'] not in s
+ else None)
+
+def per_from_id(flavors=chat_flavors+inline_flavors):
+ """
+ :param flavors:
+ ``all`` or a list of flavors
+
+ :return:
+ a seeder function that returns the from id only if the message flavor is
+ in ``flavors``.
+ """
+ return _wrap_none(lambda msg:
+ msg['from']['id']
+ if flavors == 'all' or flavor(msg) in flavors
+ else None)
+
+def per_from_id_in(s, flavors=chat_flavors+inline_flavors):
+ """
+ :param s:
+ a list or set of from id
+
+ :param flavors:
+ ``all`` or a list of flavors
+
+ :return:
+ a seeder function that returns the from id only if the from id is in ``s``
+ and message flavor is in ``flavors``.
+ """
+ return _wrap_none(lambda msg:
+ msg['from']['id']
+ if (flavors == 'all' or flavor(msg) in flavors) and msg['from']['id'] in s
+ else None)
+
+def per_from_id_except(s, flavors=chat_flavors+inline_flavors):
+ """
+ :param s:
+ a list or set of from id
+
+ :param flavors:
+ ``all`` or a list of flavors
+
+ :return:
+ a seeder function that returns the from id only if the from id is *not* in ``s``
+ and message flavor is in ``flavors``.
+ """
+ return _wrap_none(lambda msg:
+ msg['from']['id']
+ if (flavors == 'all' or flavor(msg) in flavors) and msg['from']['id'] not in s
+ else None)
+
+def per_inline_from_id():
+ """
+ :return:
+ a seeder function that returns the from id only if the message flavor
+ is ``inline_query`` or ``chosen_inline_result``
+ """
+ return per_from_id(flavors=inline_flavors)
+
+def per_inline_from_id_in(s):
+ """
+ :param s: a list or set of from id
+ :return:
+ a seeder function that returns the from id only if the message flavor
+ is ``inline_query`` or ``chosen_inline_result`` and the from id is in ``s``.
+ """
+ return per_from_id_in(s, flavors=inline_flavors)
+
+def per_inline_from_id_except(s):
+ """
+ :param s: a list or set of from id
+ :return:
+ a seeder function that returns the from id only if the message flavor
+ is ``inline_query`` or ``chosen_inline_result`` and the from id is *not* in ``s``.
+ """
+ return per_from_id_except(s, flavors=inline_flavors)
+
+def per_application():
+ """
+ :return:
+ a seeder function that always returns 1, ensuring at most one delegate is ever spawned
+ for the entire application.
+ """
+ return lambda msg: 1
+
+def per_message(flavors='all'):
+ """
+ :param flavors: ``all`` or a list of flavors
+ :return:
+ a seeder function that returns a non-hashable only if the message flavor
+ is in ``flavors``.
+ """
+ return _wrap_none(lambda msg: [] if flavors == 'all' or flavor(msg) in flavors else None)
+
+def per_event_source_id(event_space):
+ """
+ :return:
+ a seeder function that returns an event's source id only if that event's
+ source space equals to ``event_space``.
+ """
+ def f(event):
+ if is_event(event):
+ v = peel(event)
+ if v['source']['space'] == event_space:
+ return v['source']['id']
+ else:
+ return None
+ else:
+ return None
+ return _wrap_none(f)
+
+def per_callback_query_chat_id(types='all'):
+ """
+ :param types:
+ ``all`` or a list of chat types (``private``, ``group``, ``channel``)
+
+ :return:
+ a seeder function that returns a callback query's originating chat id
+ if the chat type is in ``types``.
+ """
+ def f(msg):
+ if (flavor(msg) == 'callback_query' and 'message' in msg
+ and (types == 'all' or msg['message']['chat']['type'] in types)):
+ return msg['message']['chat']['id']
+ else:
+ return None
+ return f
+
+def per_callback_query_origin(origins='all'):
+ """
+ :param origins:
+ ``all`` or a list of origin types (``chat``, ``inline``)
+
+ :return:
+ a seeder function that returns a callback query's origin identifier if
+ that origin type is in ``origins``. The origin identifier is guaranteed
+ to be a tuple.
+ """
+ def f(msg):
+ def origin_type_ok():
+ return (origins == 'all'
+ or ('chat' in origins and 'message' in msg)
+ or ('inline' in origins and 'inline_message_id' in msg))
+
+ if flavor(msg) == 'callback_query' and origin_type_ok():
+ if 'inline_message_id' in msg:
+ return msg['inline_message_id'],
+ else:
+ return msg['message']['chat']['id'], msg['message']['message_id']
+ else:
+ return None
+ return f
+
+def per_invoice_payload():
+ """
+ :return:
+ a seeder function that returns the invoice payload.
+ """
+ def f(msg):
+ if 'successful_payment' in msg:
+ return msg['successful_payment']['invoice_payload']
+ else:
+ return msg['invoice_payload']
+
+ return _wrap_none(f)
+
+def call(func, *args, **kwargs):
+ """
+ :return:
+ a delegator function that returns a tuple (``func``, (seed tuple,)+ ``args``, ``kwargs``).
+ That is, seed tuple is inserted before supplied positional arguments.
+ By default, a thread wrapping ``func`` and all those arguments is spawned.
+ """
+ def f(seed_tuple):
+ return func, (seed_tuple,)+args, kwargs
+ return f
+
+def create_run(cls, *args, **kwargs):
+ """
+ :return:
+ a delegator function that calls the ``cls`` constructor whose arguments being
+ a seed tuple followed by supplied ``*args`` and ``**kwargs``, then returns
+ the object's ``run`` method. By default, a thread wrapping that ``run`` method
+ is spawned.
+ """
+ def f(seed_tuple):
+ j = cls(seed_tuple, *args, **kwargs)
+ return j.run
+ return f
+
+def create_open(cls, *args, **kwargs):
+ """
+ :return:
+ a delegator function that calls the ``cls`` constructor whose arguments being
+ a seed tuple followed by supplied ``*args`` and ``**kwargs``, then returns
+ a looping function that uses the object's ``listener`` to wait for messages
+ and invokes instance method ``open``, ``on_message``, and ``on_close`` accordingly.
+ By default, a thread wrapping that looping function is spawned.
+ """
+ def f(seed_tuple):
+ j = cls(seed_tuple, *args, **kwargs)
+
+ def wait_loop():
+ bot, msg, seed = seed_tuple
+ try:
+ handled = j.open(msg, seed)
+ if not handled:
+ j.on_message(msg)
+
+ while 1:
+ msg = j.listener.wait()
+ j.on_message(msg)
+
+ # These exceptions are "normal" exits.
+ except (exception.IdleTerminate, exception.StopListening) as e:
+ j.on_close(e)
+
+ # Any other exceptions are accidents. **Print it out.**
+ # This is to prevent swallowing exceptions in the case that on_close()
+ # gets overridden but fails to account for unexpected exceptions.
+ except Exception as e:
+ traceback.print_exc()
+ j.on_close(e)
+
+ return wait_loop
+ return f
+
+def until(condition, fns):
+ """
+ Try a list of seeder functions until a condition is met.
+
+ :param condition:
+ a function that takes one argument - a seed - and returns ``True``
+ or ``False``
+
+ :param fns:
+ a list of seeder functions
+
+ :return:
+ a "composite" seeder function that calls each supplied function in turn,
+ and returns the first seed where the condition is met. If the condition
+ is never met, it returns ``None``.
+ """
+ def f(msg):
+ for fn in fns:
+ seed = fn(msg)
+ if condition(seed):
+ return seed
+ return None
+ return f
+
+def chain(*fns):
+ """
+ :return:
+ a "composite" seeder function that calls each supplied function in turn,
+ and returns the first seed that is not ``None``.
+ """
+ return until(lambda seed: seed is not None, fns)
+
+def _ensure_seeders_list(fn):
+ @wraps(fn)
+ def e(seeders, *aa, **kw):
+ return fn(seeders if isinstance(seeders, list) else [seeders], *aa, **kw)
+ return e
+
+@_ensure_seeders_list
+def pair(seeders, delegator_factory, *args, **kwargs):
+ """
+ The basic pair producer.
+
+ :return:
+ a (seeder, delegator_factory(\*args, \*\*kwargs)) tuple.
+
+ :param seeders:
+ If it is a seeder function or a list of one seeder function, it is returned
+ as the final seeder. If it is a list of more than one seeder function, they
+ are chained together before returned as the final seeder.
+ """
+ return (chain(*seeders) if len(seeders) > 1 else seeders[0],
+ delegator_factory(*args, **kwargs))
+
+def _natural_numbers():
+ x = 0
+ while 1:
+ x += 1
+ yield x
+
+_event_space = _natural_numbers()
+
+def pave_event_space(fn=pair):
+ """
+ :return:
+ a pair producer that ensures the seeder and delegator share the same event space.
+ """
+ global _event_space
+ event_space = next(_event_space)
+
+ @_ensure_seeders_list
+ def p(seeders, delegator_factory, *args, **kwargs):
+ return fn(seeders + [per_event_source_id(event_space)],
+ delegator_factory, *args, event_space=event_space, **kwargs)
+ return p
+
+def include_callback_query_chat_id(fn=pair, types='all'):
+ """
+ :return:
+ a pair producer that enables static callback query capturing
+ across seeder and delegator.
+
+ :param types:
+ ``all`` or a list of chat types (``private``, ``group``, ``channel``)
+ """
+ @_ensure_seeders_list
+ def p(seeders, delegator_factory, *args, **kwargs):
+ return fn(seeders + [per_callback_query_chat_id(types=types)],
+ delegator_factory, *args, include_callback_query=True, **kwargs)
+ return p
+
+from . import helper
+
+def intercept_callback_query_origin(fn=pair, origins='all'):
+ """
+ :return:
+ a pair producer that enables dynamic callback query origin mapping
+ across seeder and delegator.
+
+ :param origins:
+ ``all`` or a list of origin types (``chat``, ``inline``).
+ Origin mapping is only enabled for specified origin types.
+ """
+ origin_map = helper.SafeDict()
+
+ # For key functions that returns a tuple as key (e.g. per_callback_query_origin()),
+ # wrap the key in another tuple to prevent router from mistaking it as
+ # a key followed by some arguments.
+ def tuplize(fn):
+ def tp(msg):
+ return (fn(msg),)
+ return tp
+
+ router = helper.Router(tuplize(per_callback_query_origin(origins=origins)),
+ origin_map)
+
+ def modify_origin_map(origin, dest, set):
+ if set:
+ origin_map[origin] = dest
+ else:
+ try:
+ del origin_map[origin]
+ except KeyError:
+ pass
+
+ if origins == 'all':
+ intercept = modify_origin_map
+ else:
+ intercept = (modify_origin_map if 'chat' in origins else False,
+ modify_origin_map if 'inline' in origins else False)
+
+ @_ensure_seeders_list
+ def p(seeders, delegator_factory, *args, **kwargs):
+ return fn(seeders + [_wrap_none(router.map)],
+ delegator_factory, *args, intercept_callback_query=intercept, **kwargs)
+ return p