mirror of
https://github.com/eternnoir/pyTelegramBotAPI.git
synced 2023-08-10 21:12:57 +03:00
Merge branch 'master' of https://github.com/eternnoir/pyTelegramBotAPI
This commit is contained in:
@ -1,13 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function
|
||||
|
||||
import threading
|
||||
import time
|
||||
import logging
|
||||
import os
|
||||
import pickle
|
||||
import re
|
||||
import sys
|
||||
import six
|
||||
import threading
|
||||
import time
|
||||
|
||||
import logging
|
||||
import six
|
||||
|
||||
logger = logging.getLogger('TeleBot')
|
||||
formatter = logging.Formatter(
|
||||
@ -27,6 +29,72 @@ Module : telebot
|
||||
"""
|
||||
|
||||
|
||||
class Handler:
|
||||
"""
|
||||
Class for (next step|reply) handlers
|
||||
"""
|
||||
|
||||
def __init__(self, callback, *args, **kwargs):
|
||||
self.callback = callback
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def __getitem__(self, item):
|
||||
return getattr(self, item)
|
||||
|
||||
|
||||
class Saver:
|
||||
"""
|
||||
Class for saving (next step|reply) handlers
|
||||
"""
|
||||
|
||||
def __init__(self, handlers, filename, delay):
|
||||
self.handlers = handlers
|
||||
self.filename = filename
|
||||
self.delay = delay
|
||||
self.timer = threading.Timer(delay, self.save_handlers)
|
||||
|
||||
def start_save_timer(self):
|
||||
if not self.timer.is_alive():
|
||||
if self.delay <= 0:
|
||||
self.save_handlers()
|
||||
else:
|
||||
self.timer = threading.Timer(self.delay, self.save_handlers)
|
||||
self.timer.start()
|
||||
|
||||
def save_handlers(self):
|
||||
self.dump_handlers(self.handlers, self.filename)
|
||||
|
||||
def load_handlers(self, filename, del_file_after_loading=True):
|
||||
tmp = self.return_load_handlers(filename, del_file_after_loading=del_file_after_loading)
|
||||
if tmp is not None:
|
||||
self.handlers.update(tmp)
|
||||
|
||||
@staticmethod
|
||||
def dump_handlers(handlers, filename, file_mode="wb"):
|
||||
dirs = filename.rsplit('/', maxsplit=1)[0]
|
||||
os.makedirs(dirs, exist_ok=True)
|
||||
|
||||
with open(filename + ".tmp", file_mode) as file:
|
||||
pickle.dump(handlers, file)
|
||||
|
||||
if os.path.isfile(filename):
|
||||
os.remove(filename)
|
||||
|
||||
os.rename(filename + ".tmp", filename)
|
||||
|
||||
@staticmethod
|
||||
def return_load_handlers(filename, del_file_after_loading=True):
|
||||
if os.path.isfile(filename) and os.path.getsize(filename) > 0:
|
||||
with open(filename, "rb") as file:
|
||||
handlers = pickle.load(file)
|
||||
|
||||
if del_file_after_loading:
|
||||
os.remove(filename)
|
||||
|
||||
return handlers
|
||||
|
||||
|
||||
class TeleBot:
|
||||
""" This is TeleBot Class
|
||||
Methods:
|
||||
@ -85,6 +153,9 @@ class TeleBot:
|
||||
# key: chat_id, value: handler list
|
||||
self.next_step_handlers = {}
|
||||
|
||||
self.next_step_saver = None
|
||||
self.reply_saver = None
|
||||
|
||||
self.message_handlers = []
|
||||
self.edited_message_handlers = []
|
||||
self.channel_post_handlers = []
|
||||
@ -99,6 +170,54 @@ class TeleBot:
|
||||
if self.threaded:
|
||||
self.worker_pool = util.ThreadPool(num_threads=num_threads)
|
||||
|
||||
def enable_save_next_step_handlers(self, delay=120, filename="./.handler-saves/step.save"):
|
||||
"""
|
||||
Enable saving next step handlers (by default saving disable)
|
||||
|
||||
:param delay: Delay between changes in handlers and saving
|
||||
:param filename: Filename of save file
|
||||
"""
|
||||
self.next_step_saver = Saver(self.next_step_handlers, filename, delay)
|
||||
|
||||
def enable_save_reply_handlers(self, delay=120, filename="./.handler-saves/reply.save"):
|
||||
"""
|
||||
Enable saving reply handlers (by default saving disable)
|
||||
|
||||
:param delay: Delay between changes in handlers and saving
|
||||
:param filename: Filename of save file
|
||||
"""
|
||||
self.reply_saver = Saver(self.reply_handlers, filename, delay)
|
||||
|
||||
def disable_save_next_step_handlers(self):
|
||||
"""
|
||||
Disable saving next step handlers (by default saving disable)
|
||||
"""
|
||||
self.next_step_saver = None
|
||||
|
||||
def disable_save_reply_handlers(self):
|
||||
"""
|
||||
Disable saving next step handlers (by default saving disable)
|
||||
"""
|
||||
self.reply_saver = None
|
||||
|
||||
def load_next_step_handlers(self, filename="./.handler-saves/step.save", del_file_after_loading=True):
|
||||
"""
|
||||
Load next step handlers from save file
|
||||
|
||||
:param filename: Filename of the file where handlers was saved
|
||||
:param del_file_after_loading: Is passed True, after loading save file will be deleted
|
||||
"""
|
||||
self.next_step_saver.load_handlers(filename, del_file_after_loading)
|
||||
|
||||
def load_reply_handlers(self, filename="./.handler-saves/reply.save", del_file_after_loading=True):
|
||||
"""
|
||||
Load reply handlers from save file
|
||||
|
||||
:param filename: Filename of the file where handlers was saved
|
||||
:param del_file_after_loading: Is passed True, after loading save file will be deleted
|
||||
"""
|
||||
self.reply_saver.load_handlers(filename)
|
||||
|
||||
def set_webhook(self, url=None, certificate=None, max_connections=None, allowed_updates=None):
|
||||
return apihelper.set_webhook(self.token, url, certificate, max_connections, allowed_updates)
|
||||
|
||||
@ -880,6 +999,12 @@ class TeleBot:
|
||||
return result
|
||||
return types.Message.de_json(result)
|
||||
|
||||
def edit_message_media(self, media, chat_id=None, message_id=None, inline_message_id=None, reply_markup=None):
|
||||
result = apihelper.edit_message_media(self.token, media, chat_id, message_id, inline_message_id, reply_markup)
|
||||
if type(result) == bool: # if edit inline message return is bool not Message.
|
||||
return result
|
||||
return types.Message.de_json(result)
|
||||
|
||||
def edit_message_reply_markup(self, chat_id=None, message_id=None, inline_message_id=None, reply_markup=None):
|
||||
result = apihelper.edit_message_reply_markup(self.token, chat_id, message_id, inline_message_id, reply_markup)
|
||||
if type(result) == bool:
|
||||
@ -984,7 +1109,6 @@ class TeleBot:
|
||||
def get_sticker_set(self, name):
|
||||
"""
|
||||
Use this method to get a sticker set. On success, a StickerSet object is returned.
|
||||
:param token:
|
||||
:param name:
|
||||
:return:
|
||||
"""
|
||||
@ -1052,8 +1176,7 @@ class TeleBot:
|
||||
"""
|
||||
Registers a callback function to be notified when a reply to `message` arrives.
|
||||
|
||||
Warning: `message` must be sent with reply_markup=types.ForceReply(), otherwise TeleBot will not be able to see
|
||||
the difference between a reply to `message` and an ordinary message.
|
||||
Warning: In case `callback` as lambda function, saving reply handlers will not work.
|
||||
|
||||
:param message: The message for which we are awaiting a reply.
|
||||
:param callback: The callback function to be called when a reply arrives. Must accept one `message`
|
||||
@ -1066,17 +1189,18 @@ class TeleBot:
|
||||
"""
|
||||
Registers a callback function to be notified when a reply to `message` arrives.
|
||||
|
||||
Warning: `message` must be sent with reply_markup=types.ForceReply(), otherwise TeleBot will not be able to see
|
||||
the difference between a reply to `message` and an ordinary message.
|
||||
Warning: In case `callback` as lambda function, saving reply handlers will not work.
|
||||
|
||||
:param message: The message for which we are awaiting a reply.
|
||||
:param message_id: The id of the message for which we are awaiting a reply.
|
||||
:param callback: The callback function to be called when a reply arrives. Must accept one `message`
|
||||
parameter, which will contain the replied message.
|
||||
"""
|
||||
if message_id in self.reply_handlers.keys():
|
||||
self.reply_handlers[message_id].append({"callback": callback, "args": args, "kwargs": kwargs})
|
||||
self.reply_handlers[message_id].append(Handler(callback, *args, **kwargs))
|
||||
else:
|
||||
self.reply_handlers[message_id] = [{"callback": callback, "args": args, "kwargs": kwargs}]
|
||||
self.reply_handlers[message_id] = [Handler(callback, *args, **kwargs)]
|
||||
if self.reply_saver is not None:
|
||||
self.reply_saver.start_save_timer()
|
||||
|
||||
def _notify_reply_handlers(self, new_messages):
|
||||
for message in new_messages:
|
||||
@ -1087,11 +1211,15 @@ class TeleBot:
|
||||
for handler in handlers:
|
||||
self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"])
|
||||
self.reply_handlers.pop(reply_msg_id)
|
||||
if self.reply_saver is not None:
|
||||
self.reply_saver.start_save_timer()
|
||||
|
||||
def register_next_step_handler(self, message, callback, *args, **kwargs):
|
||||
"""
|
||||
Registers a callback function to be notified when new message arrives after `message`.
|
||||
|
||||
Warning: In case `callback` as lambda function, saving next step handlers will not work.
|
||||
|
||||
:param message: The message for which we want to handle new message in the same chat.
|
||||
:param callback: The callback function which next new message arrives.
|
||||
:param args: Args to pass in callback func
|
||||
@ -1104,15 +1232,20 @@ class TeleBot:
|
||||
"""
|
||||
Registers a callback function to be notified when new message arrives after `message`.
|
||||
|
||||
Warning: In case `callback` as lambda function, saving next step handlers will not work.
|
||||
|
||||
:param chat_id: The chat for which we want to handle new message.
|
||||
:param callback: The callback function which next new message arrives.
|
||||
:param args: Args to pass in callback func
|
||||
:param kwargs: Args to pass in callback func
|
||||
"""
|
||||
if chat_id in self.next_step_handlers.keys():
|
||||
self.next_step_handlers[chat_id].append({"callback": callback, "args": args, "kwargs": kwargs})
|
||||
self.next_step_handlers[chat_id].append(Handler(callback, *args, **kwargs))
|
||||
else:
|
||||
self.next_step_handlers[chat_id] = [{"callback": callback, "args": args, "kwargs": kwargs}]
|
||||
self.next_step_handlers[chat_id] = [Handler(callback, *args, **kwargs)]
|
||||
|
||||
if self.next_step_saver is not None:
|
||||
self.next_step_saver.start_save_timer()
|
||||
|
||||
def clear_step_handler(self, message):
|
||||
"""
|
||||
@ -1131,11 +1264,14 @@ class TeleBot:
|
||||
"""
|
||||
self.next_step_handlers[chat_id] = []
|
||||
|
||||
if self.next_step_saver is not None:
|
||||
self.next_step_saver.start_save_timer()
|
||||
|
||||
def clear_reply_handlers(self, message):
|
||||
"""
|
||||
Clears all callback functions registered by register_for_reply() and register_for_reply_by_message_id().
|
||||
|
||||
:param message_id: The message for which we want to clear reply handlers
|
||||
:param message: The message for which we want to clear reply handlers
|
||||
"""
|
||||
message_id = message.message_id
|
||||
self.clear_reply_handlers_by_message_id(message_id)
|
||||
@ -1148,6 +1284,9 @@ class TeleBot:
|
||||
"""
|
||||
self.reply_handlers[message_id] = []
|
||||
|
||||
if self.reply_saver is not None:
|
||||
self.reply_saver.start_save_timer()
|
||||
|
||||
def _notify_next_handlers(self, new_messages):
|
||||
i = 0
|
||||
while i < len(new_messages):
|
||||
@ -1155,21 +1294,22 @@ class TeleBot:
|
||||
chat_id = message.chat.id
|
||||
was_poped = False
|
||||
if chat_id in self.next_step_handlers.keys():
|
||||
handlers = self.next_step_handlers[chat_id]
|
||||
if (handlers):
|
||||
handlers = self.next_step_handlers.pop(chat_id, None)
|
||||
if handlers:
|
||||
for handler in handlers:
|
||||
self._exec_task(handler["callback"], message, *handler["args"], **handler["kwargs"])
|
||||
new_messages.pop(i) # removing message that detects with next_step_handler
|
||||
was_poped = True
|
||||
self.next_step_handlers.pop(chat_id, None)
|
||||
if (not was_poped):
|
||||
if self.next_step_saver is not None:
|
||||
self.next_step_saver.start_save_timer()
|
||||
if not was_poped:
|
||||
i += 1
|
||||
|
||||
@staticmethod
|
||||
def _build_handler_dict(handler, **filters):
|
||||
return {
|
||||
'function': handler,
|
||||
'filters': filters
|
||||
'filters' : filters
|
||||
}
|
||||
|
||||
def message_handler(self, commands=None, regexp=None, func=None, content_types=['text'], **kwargs):
|
||||
@ -1332,7 +1472,8 @@ class TeleBot:
|
||||
|
||||
return True
|
||||
|
||||
def _test_filter(self, filter, filter_value, message):
|
||||
@staticmethod
|
||||
def _test_filter(filter, filter_value, message):
|
||||
test_cases = {
|
||||
'content_types': lambda msg: msg.content_type in filter_value,
|
||||
'regexp': lambda msg: msg.content_type == 'text' and re.search(filter_value, msg.text, re.IGNORECASE),
|
||||
@ -1354,6 +1495,30 @@ class AsyncTeleBot(TeleBot):
|
||||
def __init__(self, *args, **kwargs):
|
||||
TeleBot.__init__(self, *args, **kwargs)
|
||||
|
||||
@util.async_dec()
|
||||
def enable_save_next_step_handlers(self, delay=120, filename="./.handler-saves/step.save"):
|
||||
return TeleBot.enable_save_next_step_handlers(self, delay, filename)
|
||||
|
||||
@util.async_dec()
|
||||
def enable_save_reply_handlers(self, delay=120, filename="./.handler-saves/reply.save"):
|
||||
return TeleBot.enable_save_reply_handlers(self, delay, filename)
|
||||
|
||||
@util.async_dec()
|
||||
def disable_save_next_step_handlers(self):
|
||||
return TeleBot.disable_save_next_step_handlers(self)
|
||||
|
||||
@util.async_dec()
|
||||
def disable_save_reply_handlers(self):
|
||||
return TeleBot.enable_save_reply_handlers(self)
|
||||
|
||||
@util.async_dec()
|
||||
def load_next_step_handlers(self, filename="./.handler-saves/step.save", del_file_after_loading=True):
|
||||
return TeleBot.load_next_step_handlers(self, filename, del_file_after_loading)
|
||||
|
||||
@util.async_dec()
|
||||
def load_reply_handlers(self, filename="./.handler-saves/reply.save", del_file_after_loading=True):
|
||||
return TeleBot.load_reply_handlers(self, filename, del_file_after_loading)
|
||||
|
||||
@util.async_dec()
|
||||
def get_me(self):
|
||||
return TeleBot.get_me(self)
|
||||
@ -1514,6 +1679,10 @@ class AsyncTeleBot(TeleBot):
|
||||
def edit_message_text(self, *args, **kwargs):
|
||||
return TeleBot.edit_message_text(self, *args, **kwargs)
|
||||
|
||||
@util.async_dec()
|
||||
def edit_message_media(self, *args, **kwargs):
|
||||
return TeleBot.edit_message_media(self, *args, **kwargs)
|
||||
|
||||
@util.async_dec()
|
||||
def edit_message_reply_markup(self, *args, **kwargs):
|
||||
return TeleBot.edit_message_reply_markup(self, *args, **kwargs)
|
||||
|
@ -27,8 +27,8 @@ CONNECT_TIMEOUT = 3.5
|
||||
READ_TIMEOUT = 9999
|
||||
|
||||
|
||||
def _get_req_session():
|
||||
return util.per_thread('req_session', lambda: requests.session())
|
||||
def _get_req_session(reset=False):
|
||||
return util.per_thread('req_session', lambda: requests.session(), reset)
|
||||
|
||||
|
||||
def _make_request(token, method_name, method='get', params=None, files=None, base_url=API_URL):
|
||||
@ -99,7 +99,6 @@ def get_file(token, file_id):
|
||||
|
||||
|
||||
def get_file_url(token, file_id):
|
||||
method_url = r'getFile'
|
||||
return FILE_URL.format(token, get_file(token, file_id).file_path)
|
||||
|
||||
|
||||
@ -123,6 +122,8 @@ def send_message(token, chat_id, text, disable_web_page_preview=None, reply_to_m
|
||||
:param disable_web_page_preview:
|
||||
:param reply_to_message_id:
|
||||
:param reply_markup:
|
||||
:param parse_mode:
|
||||
:param disable_notification:
|
||||
:return:
|
||||
"""
|
||||
method_url = r'sendMessage'
|
||||
@ -266,7 +267,7 @@ def send_photo(token, chat_id, photo, caption=None, reply_to_message_id=None, re
|
||||
|
||||
def send_media_group(token, chat_id, media, disable_notification=None, reply_to_message_id=None):
|
||||
method_url = r'sendMediaGroup'
|
||||
media_json, files = _convert_input_media(media)
|
||||
media_json, files = _convert_input_media_array(media)
|
||||
payload = {'chat_id': chat_id, 'media': media_json}
|
||||
if disable_notification:
|
||||
payload['disable_notification'] = disable_notification
|
||||
@ -640,6 +641,21 @@ def edit_message_caption(token, caption, chat_id=None, message_id=None, inline_m
|
||||
return _make_request(token, method_url, params=payload)
|
||||
|
||||
|
||||
def edit_message_media(token, media, chat_id=None, message_id=None, inline_message_id=None, reply_markup=None):
|
||||
method_url = r'editMessageMedia'
|
||||
media_json, file = _convert_input_media(media)
|
||||
payload = {'media': media_json}
|
||||
if chat_id:
|
||||
payload['chat_id'] = chat_id
|
||||
if message_id:
|
||||
payload['message_id'] = message_id
|
||||
if inline_message_id:
|
||||
payload['inline_message_id'] = inline_message_id
|
||||
if reply_markup:
|
||||
payload['reply_markup'] = _convert_markup(reply_markup)
|
||||
return _make_request(token, method_url, params=payload, files=file, method='post' if file else 'get')
|
||||
|
||||
|
||||
def edit_message_reply_markup(token, chat_id=None, message_id=None, inline_message_id=None, reply_markup=None):
|
||||
method_url = r'editMessageReplyMarkup'
|
||||
payload = {}
|
||||
@ -756,7 +772,8 @@ def send_invoice(token, chat_id, title, description, invoice_payload, provider_t
|
||||
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
|
||||
:param reply_to_message_id: If the message is a reply, ID of the original message
|
||||
:param reply_markup: A JSON-serialized object for an inline keyboard. If empty, one 'Pay total price' button will be shown. If not empty, the first button must be a Pay button
|
||||
:return:
|
||||
:param provider_data:
|
||||
:return:
|
||||
"""
|
||||
method_url = r'sendInvoice'
|
||||
payload = {'chat_id': chat_id, 'title': title, 'description': description, 'payload': invoice_payload,
|
||||
@ -939,11 +956,17 @@ def _convert_markup(markup):
|
||||
return markup
|
||||
|
||||
|
||||
def _convert_input_media(array):
|
||||
def _convert_input_media(media):
|
||||
if isinstance(media, types.InputMedia):
|
||||
return media._convert_input_media()
|
||||
return None, None
|
||||
|
||||
|
||||
def _convert_input_media_array(array):
|
||||
media = []
|
||||
files = {}
|
||||
for input_media in array:
|
||||
if isinstance(input_media, types.JsonSerializable):
|
||||
if isinstance(input_media, types.InputMedia):
|
||||
media_dict = input_media.to_dic()
|
||||
if media_dict['media'].startswith('attach://'):
|
||||
key = media_dict['media'].replace('attach://', '')
|
||||
|
223
telebot/types.py
223
telebot/types.py
@ -10,7 +10,7 @@ import six
|
||||
from telebot import util
|
||||
|
||||
|
||||
class JsonSerializable:
|
||||
class JsonSerializable(object):
|
||||
"""
|
||||
Subclasses of this class are guaranteed to be able to be converted to JSON format.
|
||||
All subclasses of this class must override to_json.
|
||||
@ -26,7 +26,7 @@ class JsonSerializable:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Dictionaryable:
|
||||
class Dictionaryable(object):
|
||||
"""
|
||||
Subclasses of this class are guaranteed to be able to be converted to dictionary.
|
||||
All subclasses of this class must override to_dic.
|
||||
@ -42,7 +42,7 @@ class Dictionaryable:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class JsonDeserializable:
|
||||
class JsonDeserializable(object):
|
||||
"""
|
||||
Subclasses of this class are guaranteed to be able to be created from a json-style dict or json formatted string.
|
||||
All subclasses of this class must override de_json.
|
||||
@ -457,11 +457,11 @@ class Message(JsonDeserializable):
|
||||
if not entities:
|
||||
return text
|
||||
_subs = {
|
||||
"bold": "<b>{text}</b>",
|
||||
"italic": "<i>{text}</i>",
|
||||
"pre": "<pre>{text}</pre>",
|
||||
"code": "<code>{text}</code>",
|
||||
"url": "<a href=\"{url}\">{text}</a>",
|
||||
"bold" : "<b>{text}</b>",
|
||||
"italic" : "<i>{text}</i>",
|
||||
"pre" : "<pre>{text}</pre>",
|
||||
"code" : "<code>{text}</code>",
|
||||
"url" : "<a href=\"{url}\">{text}</a>",
|
||||
"text_link": "<a href=\"{url}\">{text}</a>"
|
||||
}
|
||||
if hasattr(self, "custom_subs"):
|
||||
@ -469,6 +469,7 @@ class Message(JsonDeserializable):
|
||||
_subs[type] = self.custom_subs[type]
|
||||
utf16_text = text.encode("utf-16-le")
|
||||
html_text = ""
|
||||
|
||||
def func(text, type=None, url=None, user=None):
|
||||
text = text.decode("utf-16-le")
|
||||
if type == "text_mention":
|
||||
@ -501,6 +502,7 @@ class Message(JsonDeserializable):
|
||||
def html_caption(self):
|
||||
return self.__html_text(self.caption, self.caption_entities)
|
||||
|
||||
|
||||
class MessageEntity(JsonDeserializable):
|
||||
@classmethod
|
||||
def de_json(cls, json_string):
|
||||
@ -598,29 +600,6 @@ class Document(JsonDeserializable):
|
||||
self.file_size = file_size
|
||||
|
||||
|
||||
class Sticker(JsonDeserializable):
|
||||
@classmethod
|
||||
def de_json(cls, json_string):
|
||||
obj = cls.check_json(json_string)
|
||||
file_id = obj['file_id']
|
||||
width = obj['width']
|
||||
height = obj['height']
|
||||
thumb = None
|
||||
if 'thumb' in obj:
|
||||
thumb = PhotoSize.de_json(obj['thumb'])
|
||||
emoji = obj.get('emoji')
|
||||
file_size = obj.get('file_size')
|
||||
return cls(file_id, width, height, thumb, emoji, file_size)
|
||||
|
||||
def __init__(self, file_id, width, height, thumb, emoji=None, file_size=None):
|
||||
self.file_id = file_id
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.thumb = thumb
|
||||
self.emoji = emoji
|
||||
self.file_size = file_size
|
||||
|
||||
|
||||
class Video(JsonDeserializable):
|
||||
@classmethod
|
||||
def de_json(cls, json_string):
|
||||
@ -1092,7 +1071,7 @@ class InputVenueMessageContent(Dictionaryable):
|
||||
|
||||
def to_dic(self):
|
||||
json_dic = {'latitude': self.latitude, 'longitude': self.longitude, 'title': self.title,
|
||||
'address': self.address}
|
||||
'address' : self.address}
|
||||
if self.foursquare_id:
|
||||
json_dic['foursquare_id'] = self.foursquare_id
|
||||
return json_dic
|
||||
@ -1191,7 +1170,7 @@ class InlineQueryResultArticle(JsonSerializable):
|
||||
|
||||
class InlineQueryResultPhoto(JsonSerializable):
|
||||
def __init__(self, id, photo_url, thumb_url, photo_width=None, photo_height=None, title=None,
|
||||
description=None, caption=None, reply_markup=None, input_message_content=None):
|
||||
description=None, caption=None, parse_mode=None, reply_markup=None, input_message_content=None):
|
||||
"""
|
||||
Represents a link to a photo.
|
||||
:param id: Unique identifier for this result, 1-64 bytes
|
||||
@ -1202,6 +1181,8 @@ class InlineQueryResultPhoto(JsonSerializable):
|
||||
:param title: Title for the result.
|
||||
:param description: Short description of the result.
|
||||
:param caption: Caption of the photo to be sent, 0-200 characters.
|
||||
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or
|
||||
inline URLs in the media caption.
|
||||
:param reply_markup: InlineKeyboardMarkup : Inline keyboard attached to the message
|
||||
:param input_message_content: InputMessageContent : Content of the message to be sent instead of the photo
|
||||
:return:
|
||||
@ -1215,6 +1196,7 @@ class InlineQueryResultPhoto(JsonSerializable):
|
||||
self.title = title
|
||||
self.description = description
|
||||
self.caption = caption
|
||||
self.parse_mode = parse_mode
|
||||
self.reply_markup = reply_markup
|
||||
self.input_message_content = input_message_content
|
||||
|
||||
@ -1230,6 +1212,8 @@ class InlineQueryResultPhoto(JsonSerializable):
|
||||
json_dict['description'] = self.description
|
||||
if self.caption:
|
||||
json_dict['caption'] = self.caption
|
||||
if self.parse_mode:
|
||||
json_dict['parse_mode'] = self.parse_mode
|
||||
if self.reply_markup:
|
||||
json_dict['reply_markup'] = self.reply_markup.to_dic()
|
||||
if self.input_message_content:
|
||||
@ -1286,7 +1270,7 @@ class InlineQueryResultGif(JsonSerializable):
|
||||
|
||||
class InlineQueryResultMpeg4Gif(JsonSerializable):
|
||||
def __init__(self, id, mpeg4_url, thumb_url, mpeg4_width=None, mpeg4_height=None, title=None, caption=None,
|
||||
reply_markup=None, input_message_content=None, mpeg4_duration=None):
|
||||
parse_mode=None, reply_markup=None, input_message_content=None, mpeg4_duration=None):
|
||||
"""
|
||||
Represents a link to a video animation (H.264/MPEG-4 AVC video without sound).
|
||||
:param id: Unique identifier for this result, 1-64 bytes
|
||||
@ -1296,6 +1280,8 @@ class InlineQueryResultMpeg4Gif(JsonSerializable):
|
||||
:param mpeg4_height: Video height
|
||||
:param title: Title for the result
|
||||
:param caption: Caption of the MPEG-4 file to be sent, 0-200 characters
|
||||
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text
|
||||
or inline URLs in the media caption.
|
||||
:param reply_markup: InlineKeyboardMarkup : Inline keyboard attached to the message
|
||||
:param input_message_content: InputMessageContent : Content of the message to be sent instead of the photo
|
||||
:return:
|
||||
@ -1308,6 +1294,7 @@ class InlineQueryResultMpeg4Gif(JsonSerializable):
|
||||
self.thumb_url = thumb_url
|
||||
self.title = title
|
||||
self.caption = caption
|
||||
self.parse_mode = parse_mode
|
||||
self.reply_markup = reply_markup
|
||||
self.input_message_content = input_message_content
|
||||
self.mpeg4_duration = mpeg4_duration
|
||||
@ -1322,6 +1309,8 @@ class InlineQueryResultMpeg4Gif(JsonSerializable):
|
||||
json_dict['title'] = self.title
|
||||
if self.caption:
|
||||
json_dict['caption'] = self.caption
|
||||
if self.parse_mode:
|
||||
json_dict['parse_mode'] = self.parse_mode
|
||||
if self.reply_markup:
|
||||
json_dict['reply_markup'] = self.reply_markup.to_dic()
|
||||
if self.input_message_content:
|
||||
@ -1333,8 +1322,8 @@ class InlineQueryResultMpeg4Gif(JsonSerializable):
|
||||
|
||||
class InlineQueryResultVideo(JsonSerializable):
|
||||
def __init__(self, id, video_url, mime_type, thumb_url, title,
|
||||
caption=None, video_width=None, video_height=None, video_duration=None, description=None,
|
||||
reply_markup=None, input_message_content=None):
|
||||
caption=None, parse_mode=None, video_width=None, video_height=None, video_duration=None,
|
||||
description=None, reply_markup=None, input_message_content=None):
|
||||
"""
|
||||
Represents link to a page containing an embedded video player or a video file.
|
||||
:param id: Unique identifier for this result, 1-64 bytes
|
||||
@ -1342,6 +1331,8 @@ class InlineQueryResultVideo(JsonSerializable):
|
||||
:param mime_type: Mime type of the content of video url, “text/html” or “video/mp4”
|
||||
:param thumb_url: URL of the thumbnail (jpeg only) for the video
|
||||
:param title: Title for the result
|
||||
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or
|
||||
inline URLs in the media caption.
|
||||
:param video_width: Video width
|
||||
:param video_height: Video height
|
||||
:param video_duration: Video duration in seconds
|
||||
@ -1358,6 +1349,7 @@ class InlineQueryResultVideo(JsonSerializable):
|
||||
self.thumb_url = thumb_url
|
||||
self.title = title
|
||||
self.caption = caption
|
||||
self.parse_mode = parse_mode
|
||||
self.description = description
|
||||
self.input_message_content = input_message_content
|
||||
self.reply_markup = reply_markup
|
||||
@ -1375,6 +1367,8 @@ class InlineQueryResultVideo(JsonSerializable):
|
||||
json_dict['description'] = self.description
|
||||
if self.caption:
|
||||
json_dict['caption'] = self.caption
|
||||
if self.parse_mode:
|
||||
json_dict['parse_mode'] = self.parse_mode
|
||||
if self.reply_markup:
|
||||
json_dict['reply_markup'] = self.reply_markup.to_dic()
|
||||
if self.input_message_content:
|
||||
@ -1383,13 +1377,14 @@ class InlineQueryResultVideo(JsonSerializable):
|
||||
|
||||
|
||||
class InlineQueryResultAudio(JsonSerializable):
|
||||
def __init__(self, id, audio_url, title, caption=None, performer=None, audio_duration=None, reply_markup=None,
|
||||
input_message_content=None):
|
||||
def __init__(self, id, audio_url, title, caption=None, parse_mode=None, performer=None, audio_duration=None,
|
||||
reply_markup=None, input_message_content=None):
|
||||
self.type = 'audio'
|
||||
self.id = id
|
||||
self.audio_url = audio_url
|
||||
self.title = title
|
||||
self.caption = caption
|
||||
self.parse_mode = parse_mode
|
||||
self.performer = performer
|
||||
self.audio_duration = audio_duration
|
||||
self.reply_markup = reply_markup
|
||||
@ -1399,6 +1394,8 @@ class InlineQueryResultAudio(JsonSerializable):
|
||||
json_dict = {'type': self.type, 'id': self.id, 'audio_url': self.audio_url, 'title': self.title}
|
||||
if self.caption:
|
||||
json_dict['caption'] = self.caption
|
||||
if self.parse_mode:
|
||||
json_dict['parse_mode'] = self.parse_mode
|
||||
if self.performer:
|
||||
json_dict['performer'] = self.performer
|
||||
if self.audio_duration:
|
||||
@ -1411,13 +1408,14 @@ class InlineQueryResultAudio(JsonSerializable):
|
||||
|
||||
|
||||
class InlineQueryResultVoice(JsonSerializable):
|
||||
def __init__(self, id, voice_url, title, caption=None, performer=None, voice_duration=None, reply_markup=None,
|
||||
input_message_content=None):
|
||||
def __init__(self, id, voice_url, title, caption=None, parse_mode=None, performer=None, voice_duration=None,
|
||||
reply_markup=None, input_message_content=None):
|
||||
self.type = 'voice'
|
||||
self.id = id
|
||||
self.voice_url = voice_url
|
||||
self.title = title
|
||||
self.caption = caption
|
||||
self.parse_mode = parse_mode
|
||||
self.performer = performer
|
||||
self.voice_duration = voice_duration
|
||||
self.reply_markup = reply_markup
|
||||
@ -1427,6 +1425,8 @@ class InlineQueryResultVoice(JsonSerializable):
|
||||
json_dict = {'type': self.type, 'id': self.id, 'voice_url': self.voice_url, 'title': self.title}
|
||||
if self.caption:
|
||||
json_dict['caption'] = self.caption
|
||||
if self.parse_mode:
|
||||
json_dict['parse_mode'] = self.parse_mode
|
||||
if self.performer:
|
||||
json_dict['performer'] = self.performer
|
||||
if self.voice_duration:
|
||||
@ -1439,14 +1439,15 @@ class InlineQueryResultVoice(JsonSerializable):
|
||||
|
||||
|
||||
class InlineQueryResultDocument(JsonSerializable):
|
||||
def __init__(self, id, title, document_url, mime_type, caption=None, description=None, reply_markup=None,
|
||||
input_message_content=None, thumb_url=None, thumb_width=None, thumb_height=None):
|
||||
def __init__(self, id, title, document_url, mime_type, caption=None, parse_mode=None, description=None,
|
||||
reply_markup=None, input_message_content=None, thumb_url=None, thumb_width=None, thumb_height=None):
|
||||
self.type = 'document'
|
||||
self.id = id
|
||||
self.title = title
|
||||
self.document_url = document_url
|
||||
self.mime_type = mime_type
|
||||
self.caption = caption
|
||||
self.parse_mode = parse_mode
|
||||
self.description = description
|
||||
self.reply_markup = reply_markup
|
||||
self.input_message_content = input_message_content
|
||||
@ -1459,6 +1460,8 @@ class InlineQueryResultDocument(JsonSerializable):
|
||||
'mime_type': self.mime_type}
|
||||
if self.caption:
|
||||
json_dict['caption'] = self.caption
|
||||
if self.parse_mode:
|
||||
json_dict['parse_mode'] = self.parse_mode
|
||||
if self.description:
|
||||
json_dict['description'] = self.description
|
||||
if self.thumb_url:
|
||||
@ -1581,6 +1584,7 @@ class BaseInlineQueryResultCached(JsonSerializable):
|
||||
self.caption = None
|
||||
self.reply_markup = None
|
||||
self.input_message_content = None
|
||||
self.parse_mode = None
|
||||
self.payload_dic = {}
|
||||
|
||||
def to_json(self):
|
||||
@ -1597,12 +1601,14 @@ class BaseInlineQueryResultCached(JsonSerializable):
|
||||
json_dict['reply_markup'] = self.reply_markup.to_dic()
|
||||
if self.input_message_content:
|
||||
json_dict['input_message_content'] = self.input_message_content.to_dic()
|
||||
if self.parse_mode:
|
||||
json_dict['parse_mode'] = self.parse_mode
|
||||
return json.dumps(json_dict)
|
||||
|
||||
|
||||
class InlineQueryResultCachedPhoto(BaseInlineQueryResultCached):
|
||||
def __init__(self, id, photo_file_id, title=None, description=None, caption=None, reply_markup=None,
|
||||
input_message_content=None):
|
||||
def __init__(self, id, photo_file_id, title=None, description=None, caption=None, parse_mode=None,
|
||||
reply_markup=None, input_message_content=None):
|
||||
BaseInlineQueryResultCached.__init__(self)
|
||||
self.type = 'photo'
|
||||
self.id = id
|
||||
@ -1612,11 +1618,12 @@ class InlineQueryResultCachedPhoto(BaseInlineQueryResultCached):
|
||||
self.caption = caption
|
||||
self.reply_markup = reply_markup
|
||||
self.input_message_content = input_message_content
|
||||
self.parse_mode = parse_mode
|
||||
self.payload_dic['photo_file_id'] = photo_file_id
|
||||
|
||||
|
||||
class InlineQueryResultCachedGif(BaseInlineQueryResultCached):
|
||||
def __init__(self, id, gif_file_id, title=None, description=None, caption=None, reply_markup=None,
|
||||
def __init__(self, id, gif_file_id, title=None, description=None, caption=None, parse_mode=None, reply_markup=None,
|
||||
input_message_content=None):
|
||||
BaseInlineQueryResultCached.__init__(self)
|
||||
self.type = 'gif'
|
||||
@ -1627,12 +1634,13 @@ class InlineQueryResultCachedGif(BaseInlineQueryResultCached):
|
||||
self.caption = caption
|
||||
self.reply_markup = reply_markup
|
||||
self.input_message_content = input_message_content
|
||||
self.parse_mode = parse_mode
|
||||
self.payload_dic['gif_file_id'] = gif_file_id
|
||||
|
||||
|
||||
class InlineQueryResultCachedMpeg4Gif(BaseInlineQueryResultCached):
|
||||
def __init__(self, id, mpeg4_file_id, title=None, description=None, caption=None, reply_markup=None,
|
||||
input_message_content=None):
|
||||
def __init__(self, id, mpeg4_file_id, title=None, description=None, caption=None, parse_mode=None,
|
||||
reply_markup=None, input_message_content=None):
|
||||
BaseInlineQueryResultCached.__init__(self)
|
||||
self.type = 'mpeg4_gif'
|
||||
self.id = id
|
||||
@ -1642,6 +1650,7 @@ class InlineQueryResultCachedMpeg4Gif(BaseInlineQueryResultCached):
|
||||
self.caption = caption
|
||||
self.reply_markup = reply_markup
|
||||
self.input_message_content = input_message_content
|
||||
self.parse_mode = parse_mode
|
||||
self.payload_dic['mpeg4_file_id'] = mpeg4_file_id
|
||||
|
||||
|
||||
@ -1657,7 +1666,7 @@ class InlineQueryResultCachedSticker(BaseInlineQueryResultCached):
|
||||
|
||||
|
||||
class InlineQueryResultCachedDocument(BaseInlineQueryResultCached):
|
||||
def __init__(self, id, document_file_id, title, description=None, caption=None, reply_markup=None,
|
||||
def __init__(self, id, document_file_id, title, description=None, caption=None, parse_mode=None, reply_markup=None,
|
||||
input_message_content=None):
|
||||
BaseInlineQueryResultCached.__init__(self)
|
||||
self.type = 'document'
|
||||
@ -1668,11 +1677,12 @@ class InlineQueryResultCachedDocument(BaseInlineQueryResultCached):
|
||||
self.caption = caption
|
||||
self.reply_markup = reply_markup
|
||||
self.input_message_content = input_message_content
|
||||
self.parse_mode = parse_mode
|
||||
self.payload_dic['document_file_id'] = document_file_id
|
||||
|
||||
|
||||
class InlineQueryResultCachedVideo(BaseInlineQueryResultCached):
|
||||
def __init__(self, id, video_file_id, title, description=None, caption=None, reply_markup=None,
|
||||
def __init__(self, id, video_file_id, title, description=None, caption=None, parse_mode=None, reply_markup=None,
|
||||
input_message_content=None):
|
||||
BaseInlineQueryResultCached.__init__(self)
|
||||
self.type = 'video'
|
||||
@ -1683,11 +1693,13 @@ class InlineQueryResultCachedVideo(BaseInlineQueryResultCached):
|
||||
self.caption = caption
|
||||
self.reply_markup = reply_markup
|
||||
self.input_message_content = input_message_content
|
||||
self.parse_mode = parse_mode
|
||||
self.payload_dic['video_file_id'] = video_file_id
|
||||
|
||||
|
||||
class InlineQueryResultCachedVoice(BaseInlineQueryResultCached):
|
||||
def __init__(self, id, voice_file_id, title, caption=None, reply_markup=None, input_message_content=None):
|
||||
def __init__(self, id, voice_file_id, title, caption=None, parse_mode=None, reply_markup=None,
|
||||
input_message_content=None):
|
||||
BaseInlineQueryResultCached.__init__(self)
|
||||
self.type = 'voice'
|
||||
self.id = id
|
||||
@ -1696,11 +1708,12 @@ class InlineQueryResultCachedVoice(BaseInlineQueryResultCached):
|
||||
self.caption = caption
|
||||
self.reply_markup = reply_markup
|
||||
self.input_message_content = input_message_content
|
||||
self.parse_mode = parse_mode
|
||||
self.payload_dic['voice_file_id'] = voice_file_id
|
||||
|
||||
|
||||
class InlineQueryResultCachedAudio(BaseInlineQueryResultCached):
|
||||
def __init__(self, id, audio_file_id, caption=None, reply_markup=None, input_message_content=None):
|
||||
def __init__(self, id, audio_file_id, caption=None, parse_mode=None, reply_markup=None, input_message_content=None):
|
||||
BaseInlineQueryResultCached.__init__(self)
|
||||
self.type = 'audio'
|
||||
self.id = id
|
||||
@ -1708,6 +1721,7 @@ class InlineQueryResultCachedAudio(BaseInlineQueryResultCached):
|
||||
self.caption = caption
|
||||
self.reply_markup = reply_markup
|
||||
self.input_message_content = input_message_content
|
||||
self.parse_mode = parse_mode
|
||||
self.payload_dic['audio_file_id'] = audio_file_id
|
||||
|
||||
|
||||
@ -2041,48 +2055,61 @@ class MaskPosition(JsonDeserializable, JsonSerializable):
|
||||
|
||||
# InputMedia
|
||||
|
||||
class InputMediaPhoto(JsonSerializable):
|
||||
def __init__(self, media, caption=None, parse_mode=None):
|
||||
self.type = "photo"
|
||||
class InputMedia(JsonSerializable):
|
||||
def __init__(self, type, media, caption=None, parse_mode=None):
|
||||
self.type = type
|
||||
self.media = media
|
||||
self.caption = caption
|
||||
self.parse_mode = parse_mode
|
||||
|
||||
if util.is_string(self.media):
|
||||
self._media_name = ''
|
||||
self._media_dic = self.media
|
||||
else:
|
||||
self._media_name = util.generate_random_token()
|
||||
self._media_dic = 'attach://{0}'.format(self._media_name)
|
||||
|
||||
def to_json(self):
|
||||
return json.dumps(self.to_dic())
|
||||
|
||||
def to_dic(self):
|
||||
ret = {'type': self.type, 'media': 'attach://' + util.generate_random_token()
|
||||
if not util.is_string(self.media) else self.media}
|
||||
ret = {'type': self.type, 'media': self._media_dic}
|
||||
if self.caption:
|
||||
ret['caption'] = self.caption
|
||||
if self.parse_mode:
|
||||
ret['parse_mode'] = self.parse_mode
|
||||
return ret
|
||||
|
||||
def _convert_input_media(self):
|
||||
if util.is_string(self.media):
|
||||
return self.to_json(), None
|
||||
|
||||
class InputMediaVideo(JsonSerializable):
|
||||
def __init__(self, media, caption=None, parse_mode=None, width=None, height=None, duration=None,
|
||||
return self.to_json(), {self._media_name: self.media}
|
||||
|
||||
|
||||
class InputMediaPhoto(InputMedia):
|
||||
def __init__(self, media, caption=None, parse_mode=None):
|
||||
super(InputMediaPhoto, self).__init__(type="photo", media=media, caption=caption, parse_mode=parse_mode)
|
||||
|
||||
def to_dic(self):
|
||||
ret = super(InputMediaPhoto, self).to_dic()
|
||||
return ret
|
||||
|
||||
|
||||
class InputMediaVideo(InputMedia):
|
||||
def __init__(self, media, thumb=None, caption=None, parse_mode=None, width=None, height=None, duration=None,
|
||||
supports_streaming=None):
|
||||
self.type = "video"
|
||||
self.media = media
|
||||
self.caption = caption
|
||||
self.parse_mode = parse_mode
|
||||
super(InputMediaVideo, self).__init__(type="video", media=media, caption=caption, parse_mode=parse_mode)
|
||||
self.thumb = thumb
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.duration = duration
|
||||
self.supports_streaming = supports_streaming
|
||||
|
||||
def to_json(self):
|
||||
return json.dumps(self.to_dic())
|
||||
|
||||
def to_dic(self):
|
||||
ret = {'type': self.type, 'media': 'attach://' + util.generate_random_token()
|
||||
if not util.is_string(self.media) else self.media}
|
||||
if self.caption:
|
||||
ret['caption'] = self.caption
|
||||
if self.parse_mode:
|
||||
ret['parse_mode'] = self.parse_mode
|
||||
ret = super(InputMediaVideo, self).to_dic()
|
||||
if self.thumb:
|
||||
ret['thumb'] = self.thumb
|
||||
if self.width:
|
||||
ret['width'] = self.width
|
||||
if self.height:
|
||||
@ -2092,3 +2119,57 @@ class InputMediaVideo(JsonSerializable):
|
||||
if self.supports_streaming:
|
||||
ret['supports_streaming'] = self.supports_streaming
|
||||
return ret
|
||||
|
||||
|
||||
class InputMediaAnimation(InputMedia):
|
||||
def __init__(self, media, thumb=None, caption=None, parse_mode=None, width=None, height=None, duration=None):
|
||||
super(InputMediaAnimation, self).__init__(type="animation", media=media, caption=caption, parse_mode=parse_mode)
|
||||
self.thumb = thumb
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.duration = duration
|
||||
|
||||
def to_dic(self):
|
||||
ret = super(InputMediaAnimation, self).to_dic()
|
||||
if self.thumb:
|
||||
ret['thumb'] = self.thumb
|
||||
if self.width:
|
||||
ret['width'] = self.width
|
||||
if self.height:
|
||||
ret['height'] = self.height
|
||||
if self.duration:
|
||||
ret['duration'] = self.duration
|
||||
return ret
|
||||
|
||||
|
||||
class InputMediaAudio(InputMedia):
|
||||
def __init__(self, media, thumb=None, caption=None, parse_mode=None, duration=None, performer=None, title=None):
|
||||
super(InputMediaAudio, self).__init__(type="audio", media=media, caption=caption, parse_mode=parse_mode)
|
||||
self.thumb = thumb
|
||||
self.duration = duration
|
||||
self.performer = performer
|
||||
self.title = title
|
||||
|
||||
def to_dic(self):
|
||||
ret = super(InputMediaAudio, self).to_dic()
|
||||
if self.thumb:
|
||||
ret['thumb'] = self.thumb
|
||||
if self.duration:
|
||||
ret['duration'] = self.duration
|
||||
if self.performer:
|
||||
ret['performer'] = self.performer
|
||||
if self.title:
|
||||
ret['title'] = self.title
|
||||
return ret
|
||||
|
||||
|
||||
class InputMediaDocument(InputMedia):
|
||||
def __init__(self, media, thumb=None, caption=None, parse_mode=None):
|
||||
super(InputMediaDocument, self).__init__(type="document", media=media, caption=caption, parse_mode=parse_mode)
|
||||
self.thumb = thumb
|
||||
|
||||
def to_dic(self):
|
||||
ret = super(InputMediaDocument, self).to_dic()
|
||||
if self.thumb:
|
||||
ret['thumb'] = self.thumb
|
||||
return ret
|
||||
|
@ -1,10 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
import re
|
||||
import sys
|
||||
|
||||
import six
|
||||
from six import string_types
|
||||
|
||||
@ -243,18 +244,17 @@ def extract_arguments(text):
|
||||
:param text: String to extract the arguments from a command
|
||||
:return: the arguments if `text` is a command (according to is_command), else None.
|
||||
"""
|
||||
regexp = re.compile("\/\w*(@\w*)*\s*([\s\S]*)",re.IGNORECASE)
|
||||
regexp = re.compile("/\w*(@\w*)*\s*([\s\S]*)",re.IGNORECASE)
|
||||
result = regexp.match(text)
|
||||
return result.group(2) if is_command(text) else None
|
||||
|
||||
|
||||
def per_thread(key, construct_value):
|
||||
try:
|
||||
return getattr(thread_local, key)
|
||||
except AttributeError:
|
||||
def per_thread(key, construct_value, reset=False):
|
||||
if reset or not hasattr(thread_local, key):
|
||||
value = construct_value()
|
||||
setattr(thread_local, key, value)
|
||||
return value
|
||||
|
||||
return getattr(thread_local, key)
|
||||
|
||||
|
||||
def generate_random_token():
|
||||
|
Reference in New Issue
Block a user