This commit is contained in:
flaskfarm
2022-10-06 14:40:55 +09:00
parent b4e737a6b2
commit 4b72b7dc65
18 changed files with 6668 additions and 0 deletions

View File

@@ -0,0 +1,205 @@
import asyncio
import time
import traceback
import collections
from concurrent.futures._base import CancelledError
from . import flavor_router
from ..loop import _extract_message, _dictify
from .. import exception
class GetUpdatesLoop(object):
def __init__(self, bot, on_update):
self._bot = bot
self._update_handler = on_update
async def run_forever(self, relax=0.1, offset=None, timeout=20, allowed_updates=None):
"""
Process new updates in infinity loop
:param relax: float
:param offset: int
:param timeout: int
:param allowed_updates: bool
"""
while 1:
try:
result = await self._bot.getUpdates(offset=offset,
timeout=timeout,
allowed_updates=allowed_updates)
# Once passed, this parameter is no longer needed.
allowed_updates = None
# No sort. Trust server to give messages in correct order.
for update in result:
self._update_handler(update)
offset = update['update_id'] + 1
except CancelledError:
break
except exception.BadHTTPResponse as e:
traceback.print_exc()
# Servers probably down. Wait longer.
if e.status == 502:
await asyncio.sleep(30)
except:
traceback.print_exc()
await asyncio.sleep(relax)
else:
await asyncio.sleep(relax)
def _infer_handler_function(bot, h):
if h is None:
handler = bot.handle
elif isinstance(h, dict):
handler = flavor_router(h)
else:
handler = h
def create_task_for(msg):
bot.loop.create_task(handler(msg))
if asyncio.iscoroutinefunction(handler):
return create_task_for
else:
return handler
class MessageLoop(object):
def __init__(self, bot, handle=None):
self._bot = bot
self._handle = _infer_handler_function(bot, handle)
self._task = None
async def run_forever(self, *args, **kwargs):
updatesloop = GetUpdatesLoop(self._bot,
lambda update:
self._handle(_extract_message(update)[1]))
self._task = self._bot.loop.create_task(updatesloop.run_forever(*args, **kwargs))
self._bot.scheduler.on_event(self._handle)
def cancel(self):
self._task.cancel()
class Webhook(object):
def __init__(self, bot, handle=None):
self._bot = bot
self._handle = _infer_handler_function(bot, handle)
async def run_forever(self):
self._bot.scheduler.on_event(self._handle)
def feed(self, data):
update = _dictify(data)
self._handle(_extract_message(update)[1])
class OrderedWebhook(object):
def __init__(self, bot, handle=None):
self._bot = bot
self._handle = _infer_handler_function(bot, handle)
self._update_queue = asyncio.Queue(loop=bot.loop)
async def run_forever(self, maxhold=3):
self._bot.scheduler.on_event(self._handle)
def extract_handle(update):
try:
self._handle(_extract_message(update)[1])
except:
# Localize the error so message thread can keep going.
traceback.print_exc()
finally:
return update['update_id']
# Here is the re-ordering mechanism, ensuring in-order delivery of updates.
max_id = None # max update_id passed to callback
buffer = collections.deque() # keep those updates which skip some update_id
qwait = None # how long to wait for updates,
# because buffer's content has to be returned in time.
while 1:
try:
update = await asyncio.wait_for(self._update_queue.get(), qwait)
if max_id is None:
# First message received, handle regardless.
max_id = extract_handle(update)
elif update['update_id'] == max_id + 1:
# No update_id skipped, handle naturally.
max_id = extract_handle(update)
# clear contagious updates in buffer
if len(buffer) > 0:
buffer.popleft() # first element belongs to update just received, useless now.
while 1:
try:
if type(buffer[0]) is dict:
max_id = extract_handle(buffer.popleft()) # updates that arrived earlier, handle them.
else:
break # gap, no more contagious updates
except IndexError:
break # buffer empty
elif update['update_id'] > max_id + 1:
# Update arrives pre-maturely, insert to buffer.
nbuf = len(buffer)
if update['update_id'] <= max_id + nbuf:
# buffer long enough, put update at position
buffer[update['update_id'] - max_id - 1] = update
else:
# buffer too short, lengthen it
expire = time.time() + maxhold
for a in range(nbuf, update['update_id']-max_id-1):
buffer.append(expire) # put expiry time in gaps
buffer.append(update)
else:
pass # discard
except asyncio.TimeoutError:
# debug message
# print('Timeout')
# some buffer contents have to be handled
# flush buffer until a non-expired time is encountered
while 1:
try:
if type(buffer[0]) is dict:
max_id = extract_handle(buffer.popleft())
else:
expire = buffer[0]
if expire <= time.time():
max_id += 1
buffer.popleft()
else:
break # non-expired
except IndexError:
break # buffer empty
except:
traceback.print_exc()
finally:
try:
# don't wait longer than next expiry time
qwait = buffer[0] - time.time()
if qwait < 0:
qwait = 0
except IndexError:
# buffer empty, can wait forever
qwait = None
# debug message
# print ('Buffer:', str(buffer), ', To Wait:', qwait, ', Max ID:', max_id)
def feed(self, data):
update = _dictify(data)
self._update_queue.put_nowait(update)